클로저가 값을 캡처할 때 값 캡쳐(Value)와 참조 캡쳐(Reference)로 나뉜다. 이는 주로 값 타입과 참조 타입의 특성에 의해 결정된다. 예를 들어 클로저에 의해 캡쳐되는 것이 클래스라면 클래스는 참조 타입이므로 그 인스턴스에 대한 "참조"를 캡쳐한다.
class MyClass {
var value = 10
}
func captureReference() -> () -> Void {
let instance = MyClass() // 1. 클래스이므로
let closure = {
print("Value is \(instance.value)")//2.⭐️참조 캡쳐
}
instance.value = 20 // 인스턴스의 상태 변화
return closure
}
let myClosure = captureReference()
myClosure() //3. 20이 출력된다
- 클로저가 클래스 인스턴스를 캡쳐했고 클래스는 참조타입이므로 인스턴스의 상태가 변할 때 클로저에서도 그 변화를 반영한다.
struct MyStruct {
var value = 10
}
func captureValue() -> () -> Void {
var instance = MyStruct() //1.스트럭트
let closure = {
print("Value is \(instance.value)") //2.이므로 값 캡쳐
}
instance.value = 20
return closure
}
let myClosure = captureValue()
myClosure() // 3.10이 출력된다.
클래스와 구조체라는 것 빼고는 같은 상황. 그러나 출력 값이 10인걸 볼 수 있다.
이는 클로저나 구조체나 열거형같은 값 타입을 캡쳐할 때 복사본을 캡쳐하기 때문이다. 따라서 캡쳐된 값은 변하지않고 원본이 변경되더라도 클로저 내의 값에는 영향을 미치지 않는다.
클로저의 강한 참조 순환을 방지하기 위해 캡쳐 리스트를 사용한다. 이를 통해 약한 참조(weak)나 미소유 참조(unowned)로 캡쳐할 수 있다.
class MyClass {
var value = 10
}
func captureWithList() -> () -> Void {
let instance = MyClass()
let closure = { [weak instance] in
if let instance = instance {
print("Value is \(instance.value)")// ⭐️
} else {
print("Instance is nil")
}
}
return closure
}
let myClosure = captureWithList()
myClosure() // 출력: Value is 10 (또는 Instance is nil, 인스턴스의 생명 주기에 따라 다름)