J
[Swift] Escaping Closure (@escaping) 본문
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 |