Swift에서 @escaping은 클로저가 함수나 메서드의 실행이 종료된 후에도 호출될 수 있음을 나타냅니다.
기본적으로 클로저는 함수 내에서만 유효하고, 함수가 종료되면 클로저도 메모리에서 해제됩니다.
그러나, 클로저가 함수 밖에서 나중에 호출되거나 저장하려면 @escaping으로 명시해야 합니다.
import Foundation
class MyClass {
var storedClosure: () -> () = {}
func store(closure: () -> ()) {
storedClosure = closure
}
}
func async(closure: () -> ()) {
DispatchQueue.main.async {
closure()
}
}
@escaping을 사용하면 reference counting을 이용하여 클로저가 함수의 스택 프레임에 벗어나도 클로저를 메모리에 유지합니다.
때문에 [weak self] 와 같은 방법을 이용하여 메모리 관리에 신경을 써야 합니다.
여러 thread에서 동시에 함수나 클로저를 실행하는 경우 race condition이 발생할 수 있습니다.
@Sendable은 다른 thread에서 실행될 때에도 안전하게 동기화될 수 있도록 보장하는 attribute입니다.
import Foundation
actor Counter {
var count = 0
}
let counter = Counter()
let closure: @Sendable () -> Void = {
counter.count += 1
}
iOS에서 UI 업데이트는 반드시 main thread에서 실행되도록 제한됩니다.
UI 업데이트는 최적화를 위해 thread safe하게 설계되지 않았고, 업데이트에 순서가 정해져 있기 때문입니다.
import Foundation
@MainActor func mainActorFunction() {
print("run on main")
}
func runClosure(_ closure: @escaping @Sendable () -> Void) {
Task {
closure()
}
}
runClosure {
mainActorFunction()
}
이를 해결하려면 아래와 같이 @MainActor를 closure에 붙여주면 됩니다.
func runClosure(_ closure: @escaping @Sendable @MainActor () -> Void) {
Task {
await closure()
}
}
클로저를 명시적으로 작성하지 않고도 자동으로 변환해주는 속성입니다.
쉽게 말해 클로저를 인자로 전달할 때 { } 를 사용하지 않아도 동작할 수 있도록 해줍니다.
import Foundation
func bool(_ closure: () -> Bool) {
closure() ? print("true") : print("false")
}
bool { 2 > 1 }
func bool2(_ closure: @autoclosure () -> Bool) {
closure() ? print("true") : print("false")
}
bool2 (2 > 1)
이제 우리는 아래와 같은 함수를 이해할 수 있습니다. 아마도
func nowWeCanUnderstand(_ closure: @autoclosure @MainActor @Sendable @escaping () -> Void) {
Task {
await closure()
}
}
nowWeCanUnderstand(print("hello world"))