Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

J

[Swift] Escaping Closure (@escaping) 본문

Swift

[Swift] Escaping Closure (@escaping)

yujaehui 2024. 4. 5. 12:23

1. Escaping Closure란?

Swift에서 클로저(Closure)는 코드 블록을 캡슐화하여 나중에 실행하거나 전달할 수 있는 강력한 도구.

클로저는 기본적으로 함수 내부에서 실행되도록 설계되어 있지만, 특정 상황에서는 함수가 종료된 이후에도 실행될 수 있음.

이런 경우, 클로저가 함수의 스코프를 벗어날 수 있다는 것을 나타내기 위해 `@escaping` 키워드를 사용.


2. Escaping Closure의 정의

`@escaping` 클로저란, 함수의 수명보다 더 오래 살아남는 클로저를 의미.

이는 클로저가 함수의 실행이 종료된 후에도 외부에서 참조되거나 호출될 가능성이 있는 경우를 나타냄.


3. Escaping Closure의 주요 특징

  • 함수의 스코프를 벗어남.
    • 클로저가 함수 외부로 전달되거나 저장될 경우.
    • 비동기 작업에서 주로 사용됨.
  • 명시적으로 선언 필요
    • `@escaping` 키워드를 사용하여 클로저가 escaping임을 나타냄.
  • Capturing 문제
    • 클로저 내부에서 `self`를 사용할 경우, 캡처 리스트를 사용하거나 `[weak self]`로 참조를 약하게 만들어야 메모리 누수 방지 가능.

4. 기본 문법

1. Non-escaping (기본)

func performTask(task: () -> Void) {
    task()
}

performTask {
    print("This task is non-escaping.")
}
  • 기본적으로 Swift의 클로저는 non-escaping.
  • 클로저는 함수 내부에서만 실행되며, 함수 종료 후에는 더 이상 참조되지 않음.

2. Escaping Closure

var completionHandlers: [() -> Void] = []

func addCompletionHandler(handler: @escaping () -> Void) {
    completionHandlers.append(handler)
}

addCompletionHandler {
    print("This task will escape.")
}

// 나중에 실행
completionHandlers.forEach { $0() }
  • `@escaping` 키워드는 클로저가 함수의 범위를 벗어나더라도 나중에 실행될 수 있음을 나타냄.

5. Escaping Closure의 활용 사례

1. 비동기 작업

비동기 작업에서는 클로저가 작업 완료 시 호출되므로 escaping이 필요.

func fetchData(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        // 비동기 작업
        sleep(2)
        let data = "Fetched Data"

        DispatchQueue.main.async {
            completion(data) // 함수 스코프를 벗어난 클로저 실행
        }
    }
}

fetchData { data in
    print(data) // "Fetched Data"
}

 

2. 클로저 저장

클로저를 배열이나 다른 변수에 저장할 때는 escaping 클로저가 필요.

var storedClosures: [() -> Void] = []

func storeClosure(closure: @escaping () -> Void) {
    storedClosures.append(closure)
}

storeClosure {
    print("Stored closure executed.")
}

// 나중에 저장된 클로저 실행
storedClosures.forEach { $0() }

6. Capturing self와 메모리 관리

`@escaping` 클로저 내부에서 self를 캡처할 경우, 강한 순환 참조(Strong Reference Cycle)가 발생할 수 있음.

이를 방지하려면 `[weak self]`나 `[unowned self]`를 사용.

 

`[weak self]` 사용 예제

class ViewController {
    var name = "Swift"

    func executeTask() {
        fetchData { [weak self] data in
            guard let self = self else { return }
            print("\\(self.name) received: \\(data)")
        }
    }
}

 

`[unowned self]` 사용 예제

`self`가 항상 존재한다고 확신할 수 있을 때 `[unowned self]`를 사용할 수 있음.

class ViewController {
    var name = "Swift"

    func executeTask() {
        fetchData { [unowned self] data in
            print("\\(self.name) received: \\(data)")
        }
    }
}

7. Escaping과 Non-Escaping의 차이

  Non-Escaping Escaping
함수 스코프 내부에서 실행 클로저는 함수 내부에서만 실행 함수가 종료된 후에도 실행 가능
`@escaping` 키워드 필요 여부 필요 없음 필요
비동기 작업 지원하지 않음 필수
메모리 관리 `self` 캡처 불필요 `self` 캡처 시 `weak`/`unowned` 필요

8. `@escaping` 키워드가 필요한 이유

  • 안전성 보장
    • 함수의 실행이 끝난 후 클로저가 실행될 가능성을 명시적으로 나타냄으로써, 클로저의 생명 주기에 대한 이해를 도움.
  • 코드 가독성
    • 클로저가 escaping인지 아닌지를 명확히 구분하여 코드 가독성을 높임.
  • 컴파일러 최적화 지원
    • Swift 컴파일러는 non-escaping 클로저의 성능을 최적화할 수 있으므로, 필요할 때만 escaping으로 선언하는 것이 좋음.

9. 결론

`@escaping`은 Swift에서 클로저의 수명 주기를 명확히 하고, 비동기 작업이나 클로저 저장과 같은 상황에서 사용.

하지만 필요하지 않은 곳에서는 `@escaping`을 피하는 것이 성능 및 메모리 관리 측면에서 유리.

클로저를 사용할 때는 `self` 캡처로 인한 메모리 누수를 방지하는 것도 중요.

'Swift' 카테고리의 다른 글

[Swift] mutating  (0) 2024.04.12
[Swift] inout  (0) 2024.04.08
[Swfit] Value Type, Reference Type (값 타입과 참조 타입)  (0) 2024.04.04
[Swift] Optimization Tips  (0) 2024.04.04
[Swift] WMO(Whole Module Optimization)  (0) 2024.04.03