Swift Closure Capturing Values, 클로저 값 캡처
1. 클로저 값 캡쳐란?
• 클로저 외부의 변수를 클로저 내부에 저장 해 두 는 것을 말한다!
예제를 통해 확인 해보자!
func testFunction() {
var name = "kim"
var age = 13
var closure: () -> Void = {
print("나이는: \(age)") // age의 값이 캡쳐 되었다!
}
closure() // 나이는: 13
}
위의 예제를 보면 age가 클로저에 의해 캡쳐 되었다! name은 캡처 되지 않았다! 사용 안 했으니까!
2. 다음은 클로저의 값 캡처 방식에 대하여 알아보자!
결론 부터 말하면 클로저에서는 값을 캡쳐 할때 Reference Capture 한다!
func testFunction() {
var name = "kim"
var age = 13
print("첫 번째 나이는: \(age)")
var closure: () -> Void = {
print("세 번째 나이는: \(age)") // age의 값이 캡쳐 되었다!
}
age = 21
print("두 번째 나이는: \(age)")
closure()
}
/*
첫 번째 나이는: 13
두 번째 나이는: 21
세 번째 나이는: 21
*/
예제를 보면 참조 타입으로 캡처를 하기 때문에 age의 값이 변함에 따라 closure 내부의 age 또한 변함을 알 수 있다.
그러면 age를 값 타입으로 저장할 수는 없을까? Capture List를 활용하면 값타입으로 저장 할 수 있다!!
예제를 확인 해봅시다
func testFunction() {
var name = "kim"
var age = 13
print("첫 번째 나이는: \(age)")
var closure: () -> Void = { [age] in
print("세 번째 나이는: \(age)") // age의 값이 캡쳐 되었다!
}
age = 21
print("두 번째 나이는: \(age)")
closure()
}
/*
첫 번째 나이는: 13
두 번째 나이는: 21
세 번째 나이는: 13
*/
예제를 보면 값타입으로 Capture 되어서 age가 13으로 출력 되는 것을 알 수 있다.
3. ARC 와 클로저!
먼저 ARC(Auto Reference Counting) 지식이 없으시면 ARC 글을 읽어 보고 오셔도 좋을 것 같아요
Closure로 값을 캡쳐하면 참조 타입으로 캡처 하기 때문에 강한 순한 참조가 발생 하는데 예제를 통해서 확인 해봅시다!
class Human {
var name = ""
lazy var closure: () -> String = { // 기본적으로 일반 변수들은 클래스가 생성된 이후에 사용 할 수 있따.
print(self.name)
return "저의 이름은 \(self.name)입니다"
}
init(name: String) {
self.name = name
print("init")
}
deinit {
print("deinit")
}
}
var human: Human? = Human(name: "Kim")
human?.closure()
human = nil
/*
init
Kim
*/
우선 lazy를 사용하는 이유를 말씀드리면 class의 일반 변수들은 클래스가 생성된 이후에 사용할 수 있음으로 lazy를 통해 객체 생성 후 실행 시 name을 사용하기 위하여 lazy를 사용하였습니다
예제를 보시면 객체를 생성 후 closure 함수를 실행시켜 주고 객체에 nil을 넣어 주어도 deinit이 실행이 안 되는 것을 확인할 수 있습니다.
왜그럴까?
그 이유는 먼저 human의 객체가 생성 시에 Reference count가 1 증가 하게 되고 closure에서 name변수를 캡쳐 시에 참조 타입으로 캡처를 하기 때문에 Reference count가 1 증가 해서 human에 nil 값을 넣어주어도 메모리에서 해제가 되지 않는 것입니다.
해결방법은?
ARC에서 변수 앞에 weak, unowned를 통하여 참조 카운터를 증가 시켜주지 않는 방법으로 해결을 하였는데 Closure에서도 같은 방식으로 Capture List에 self에 대한 참조를 weak, unowned로 캡쳐 하도록 하면 된다!
class Human {
var name = ""
lazy var closure: () -> String = { [weak self] in // 기본적으로 일반 변수들은 클래스가 생성된 이후에 사용 할 수 있따.
print(self?.name)
return "저의 이름은 \(self?.name)입니다"
}
init(name: String) {
self.name = name
print("init")
}
deinit {
print("deinit")
}
}
var human: Human? = Human(name: "Kim")
human?.closure()
human = nil
/*
init
Kim
deinit
*/
정상적으로 deinit이 출력 됩니다! 또한 Capture List를 통하여 값타입으로 캡쳐 하여도 문제없이 deinit이 실행 되는 것을 확인 할 수있습니다.
class Human {
var name = ""
lazy var closure: () -> String = { [name] in // 기본적으로 일반 변수들은 클래스가 생성된 이후에 사용 할 수 있따.
print(name)
return "저의 이름은 \(name)입니다"
}
init(name: String) {
self.name = name
print("init")
}
deinit {
print("deinit")
}
}
var human: Human? = Human(name: "Kim")
human?.closure()
human?.name = "Park"
human?.closure() // 값타입이라 Kim으로 출력
human = nil
/*
init
Kim
Kim
deinit
*/