@escaping
키워드 작성var closures: [() -> Void] = []
func addClosure(closure: @escaping () -> Void) {
closures.append(closure)
}
클로저를 저장할 배열 closures
,
closures
에 클로저를 추가할 메서드 addClosure
를 구현했다.
addClosure
을 통해 전달된 인수 클로저는 closures
에 추가만 되고 아직 실행되지는 않는다.
addClosure { print("escaping closure1... 🐌") }
addClosure { print("escaping closure2... 🐌") }
closures.forEach { $0() } // 클로저 실행
addClosure
이 종료된 후 클로저를 실행할 경우에
추가된 클로저가 실행될 수 있도록 전달되는 인수는 escaping 클로저로 선언되어야 한다.
즉, 함수가 종료된 후에도 해당 클로저를 사용할 수 있게 하기 위해 escaping 클로저로 작성해야 한다.
@escaping
으로 작성하지 않으면 아래의 오류가 발생한다.
Converting non-escaping parameter 'closure' to generic parameter 'Element' may allow it to escape
해당 오류는 escaping 클로저가 필요한 곳에 non-escaping 클로저로 작성했다는 의미이다.
이처럼 escaping 클로저는 함수가 종료된 후에 클로저가 실행되어야 하는 경우에 사용할 수 있다.
대다수가 비동기 로직과 관련있다.
var closures: [() -> Void] = []
func doSomethingWithEscapingClosure(closure: @escaping () -> Void) {
closures.append(closure)
}
func doSomethingWithClosure(closure: () -> Void) {
closure()
}
doSomethingWithEscapingClosure
doSomethingWithClosure
class TestClass {
var x = 10
func testMethod() {
doSomethingWithClosure { x = 100 }
}
}
TestClass
를 만들고 testMethod
를 구현해 보자.
doSomethingWithClosure
을 호출하고 x를 100으로 변경해 보았다.
self
를 암시적으로 참조할 수 있다.
func testMethod() {
// doSomethingWithClosure { x = 100 }
doSomethingWithEscapingClosure { x = 200 } // error! 🚨
}
똑같이 doSomethingWithEscapingClosure
을 호출해 보자.
이번엔 self
를 명시적으로 참조해야 한다는 에러가 발생한다.
escaping 클로저는 함수가 종료된 이후에도 실행될 수 있다.
즉, escaping 클로저가 캡처한 self가 이미 메모리에서 해제되었을 가능성이 있다.
따라서 명시적으로 self
를 작성함을 통해 순환 참조의 가능성을 인지할 수 있도록 한다.
class TestClass {
var x = 10
func testMethod() {
doSomethingWithClosure { x = 100 }
doSomethingWithEscapingClosure { self.x = 200 }
}
}
let tc = TestClass()
tc.testMethod()
print(tc.x) // 100
closures.first?()
print(tc.x) // 200
인스턴스를 생성하고 각각 x를 호출해 보면 다음과 같이 출력된다.
doSomethingWithEscapingClosure { [weak self] in
self?.x = 200
}
물론 escaping 클로저일 때는 위와 같이 weak self로 참조하는 것이 가장 안전하다.
cf. non-escaping 클로저는 함수가 종료되면 해당 클로저는 더이상 사용되지 않는 것이 명확하기 때문에 메모리가 해제되어 순환 참조가 발생하지 않는다.
인수로 전달된 클로저가 함수가 종료된 후에 실행될 때는, escaping
클로저로 작성해 주어야 한다.