iOS/Swift

Swift에서 closure 함수 쓸 때 사용되는 @escaping 은 뭘까?

태애니 2025. 5. 14. 23:22
728x90

@escaping 키워드는 Swift에서 클로저가 함수의 생명 주기 밖에서도 사용될 수 있을 때 사용한다.
클로저가 함수가 반환된 후에도 실행될 수 있다면, 해당 클로저 파라미터에 @escaping을 붙여야 한다.

보통 비동기 작업에서 자주 사용된다. 예를 들어, 네트워크 요청이 끝난 뒤 결과를 처리하는 클로저가 이에 해당한다.

 

 

 

언제 @escaping을 사용해야 하는가?

클로저가 다음 중 하나라도 해당되면 @escaping을 붙여야 한다.

  1. 함수가 종료된 이후에 클로저가 실행된다.
  2. 클로저가 저장 프로퍼티로 저장된다.
  3. 클로저가 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 등을 통해 지연 실행되는 경우 비동기 큐에 전달되어 나중에 실행되므로, 함수 실행 이후에도 클로저가 살아있다

 

 

 

 

 

 

728x90