@escaping 키워드는 Swift에서 클로저가 함수의 생명 주기 밖에서도 사용될 수 있을 때 사용한다.
클로저가 함수가 반환된 후에도 실행될 수 있다면, 해당 클로저 파라미터에 @escaping을 붙여야 한다.
보통 비동기 작업에서 자주 사용된다. 예를 들어, 네트워크 요청이 끝난 뒤 결과를 처리하는 클로저가 이에 해당한다.
언제 @escaping을 사용해야 하는가?
클로저가 다음 중 하나라도 해당되면 @escaping을 붙여야 한다.
- 함수가 종료된 이후에 클로저가 실행된다.
- 클로저가 저장 프로퍼티로 저장된다.
- 클로저가 DispatchQueue, OperationQueue 등에 의해 나중에 호출된다.
func fetchData(completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
// 비동기 작업 (예: 네트워크 요청 시뮬레이션)
sleep(2)
let result = "데이터 불러오기 완료"
// 메인 스레드에서 클로저 호출
DispatchQueue.main.async {
completion(result)
}
}
}
// 함수 사용
fetchData { result in
print("결과: \(result)")
}
@escaping이 없으면 컴파일 에러임.
이유는 completion이 함수가 끝난 후 실행되기 때문이다.
class ClosureHolder {
var savedClosure: (() -> Void)?
func saveClosure(closure: @escaping () -> Void) {
savedClosure = closure
}
func executeClosure() {
savedClosure?()
}
}
let holder = ClosureHolder()
holder.saveClosure {
print("저장된 클로저 실행")
}
holder.executeClosure() // 나중에 실행됨
해당 클로저는 함수가 반환된 뒤에도 살아있다.
여기서 헷갈릴 수 있는데 캡쳐링인데, 캡쳐링이랑은 조금 다르다.
여기에서 “캡처링”과 “함수 종료 이후에도 살아있다”는 것은 별개 개념이지만 함께 묶여 있는 것처럼 보일 수 있지만!!!
해당 클로저는 함수가 반환된 뒤에도 살아있지만, 이것이 곧 '캡처링'이라는 뜻은 아니다.
'캡처링'은 클로저가 외부 값을 참조할 때 발생하는 개념이며,
함수 종료 이후에도 클로저가 실행된다는 것은 클로저의 생명주기와 관련된 별개의 개념이다.
이 두 개념은 종종 함께 나타나지만, 동일한 것은 아니다.
📌 예를 들어, @escaping 클로저가 아니더라도 클로저 내부에서 self를 참조하면 캡처링은 발생한다.
캡처링 여부는 @escaping과 관계없이 클로저가 외부 값을 참조하느냐에 따라 결정된다.
그럼 저런 현상은 무엇인가? = 순환 참조이다.
아래의 예시를 참고하면 된다.
클로저는 self.name을 참조하고 있다.
클로저 내부에서 self를 사용하면, self가 캡처된다.
따라서 이 클로저는 Person 인스턴스가 살아 있는 한 self를 계속 강하게 참조하게 된다.
class Person {
var name = "민수"
func doSomething() {
let holder = ClosureHolder()
holder.saveClosure {
print("이름은 \(self.name)입니다") // self 캡처됨
}
holder.executeClosure()
}
}
그래서 알아둬야하는게: 순환 참조(Retain Cycle)
ClosureHolder가 Person을 저장하고, Person이 클로저를 통해 ClosureHolder를 참조하면
순환 참조가 발생할 수 있다.
이를 방지하기 위해서는 [weak self], [unowned self] 같은 캡처 리스트를 사용해야 한다.
holder.saveClosure { [weak self] in
print("이름은 \(self?.name ?? "없음")입니다")
}
// 순환참조 방지하기
func performAsyncTask(task: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
task()
}
}
performAsyncTask {
print("지연된 작업 실행")
}
클로저가 DispatchQueue 등을 통해 지연 실행되는 경우 비동기 큐에 전달되어 나중에 실행되므로, 함수 실행 이후에도 클로저가 살아있다
'iOS > Swift' 카테고리의 다른 글
[문법/키워드] Computed Property / Extension (0) | 2025.05.21 |
---|---|
[문법/키워드] mutating, Associated Value (연관 값) (0) | 2025.05.20 |
Swift Closure (0) | 2025.05.03 |
Swift 접근 제어 요약 (0) | 2025.05.02 |
내가 더 이상 헷갈리기 싫어서 적는 Swift의 핵심 구성 요소 (0) | 2025.05.01 |