swift 프로그래밍에서 클로져를 사용하는 것은 무척 당연스러운 일이다.
기술 블로그 서핑중에 capture list라는 단어를 보고 스스로 정확한 답변을 하고싶어 글을 쓰게 됐다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var total = 0
let incrementer = { [amount] in
total += amount
return total
}
return incrementer
}
var incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // 결과는 10
incrementByTen() // 결과는 20
// 클로저 해제
incrementByTen = nil // incrementByTen에 저장된 클로저에 대한 참조를 nil로 설정
클로져의 메모리 캡쳐에 대해 모른다면 total은 지역변수이기에 브라켓이 끝나는 순간 생명주기도 끝날텐데 왜 결과가 저렇게 나올까라는 의문을 가질 수 있다.
클로저내에서 외부 지역변수를 사용한다면 해당 지역변수는 클로저참조가 없어질때까지 생명주기를 유지한다.
그럼 이제 우리가 자주 사용하는 [weak self]
, [unowned self]
에 대해 고찰해보자.
weak나 unowned 키워드는
순환참조를 없애 메모리 누수는 해결한다는 걸 알고 있을것이다.
그렇다면 []
는 어떤 의미를 가질까? 만약 대괄호 없으면 (메모리 누수를 제외하면) 무슨 상황이 벌어질까?
memory capture와 capture list를 먼저 이해 할 필요가 있다.
capture list란 []
안에 리스팅된 변수들을 의미한다.
let ref = Class()
let val = Struct()
let closure = { [ref, val]
...
}
ref
, val
이 capture list의 요소들로, 클로져가 정의된 시점에 복사가 일어난다.
struct Struct {
var x: Int
init(x: Int) {
self.x = x
}
}
class Class {
var x: Int
init(x: Int) {
self.x = x
}
}
var refCapture = Class(x: 1)
var refCapturelist = Class(x: 1)
var valCapture = Struct(x: 1)
var valCapturelist = Struct(x: 1)
let closure = { [refCapturelist, valCapturelist] in
print("refCapture.x = \(refCapture.x)\n")
print("refCapturelist.x = \(refCapturelist.x)\n")
print("valCapture.x = \(valCapture.x)\n")
print("valCapturelist.x = \(valCapturelist.x)\n")
}
closure()
refCapture.x = 2
refCapturelist.x = 2
valCapture.x = 2
valCapturelist.x = 2
closure()
결과값은:
refCapture.x = 2
refCapturelist.x = 2
valCapture.x = 2
valCapturelist.x = 1
refCapture와 valCapture는 메모리 캡쳐이므로 값이 바뀌는 것은 당연하다.
그렇다면 refCapturelist와 valCapturelist의 왜 차이를 보이는 것일까?
capture list에 기입된 것들은, 클로터 정의 시점에 주어진 요소들의 값복사가 일어난다고 위에서 밝혔다.
여기서 포인터에 대한 지식이 필요한데, refCapturelist 변수의 내부엔 주소값이 들어있기 때문이다. 만약 값복사가 일어나더라도 주소값을 다른 변수에 저장하는 것 뿐이므로, 주소가 가리키는 원형이 수정되는 것이다.
반면 valCapturelist는 call by value이므로 이전의 값이 캡쳐되어 수정이 안된 것처럼 보이는 것이다.
print(valCapturelist.x)
// 결과값: 2
클로저 밖의 값은 위 결과처럼 수정된 것을 확인할 수 있다.
외부 변수들이 closure에 들어오면 해당변수에 접근 가능하게 되고, 접근한 값의 retain값이 증가한다.
외부 변수들을 [] 괄호안에 넣으면 카피본을 클로져 내부에서 사용하게 된다.
[]는 선언 순간부터 외부 변수를 복사해 가지고 있는다.
이 때 변수가 class일 경우, 변수안의 주소값이 복사되므로 원본이 복사되지 않는 상태이다.
weak를 사용하지 않으면 원본의 retain이 증가하는 것이다.
요약하면
1. 괄호를 사용하지 않으면 접근, 괄호를 사용하면 복사.
2. 복사는 클로져 선언 때 이루어진다.
2. 복사를 해도 class는 딥카피가 아니므로, 변수에 접근한다면 원본에 접근이된다.