클로저의 캡처 (Capturing Values)
클로저는 왜 캡처를 사용하는가
- 클로저는 외부 변수나 상수를 내부에서 사용할 수 있도록 캡처할 수 있다
- 클로저가 외부 변수에 대한 참조를 유지하기 때문에 실행 시점에도 해당 값을 사용할 수 있다
- 캡처된 값은 클로저가 메모리에 존재하는 동안 유지된다
- 클로저가 변수에 저장되거나 함수의 반환값으로 사용될 때 주로 발생한다
캡처
- 클로저가 자신이 정의된 외부의 변수나 상수를 참조할 수 있는 기능이다
- 클로저는 외부에서 선언된 변수를 복사하지 않고 참조를 통해 유지한다
- 클로저가 변수에 저장되거나 함수의 반환값이 되면 해당 변수는 힙 메모리에 유지된다
- 이로 인해 클로저는 상태를 기억할 수 있으며, 동일한 참조를 여러 곳에서 공유할 수 있다
클로저가 외부 변수를 캡처하는 예
var total = 0
let accumulate = { (value: Int) -> Int in
total += value
return total
}
accumulate(2)
accumulate(3)
accumulate(5)
total = 100
accumulate(1)
- 클로저는 변수
total을 캡처하고 있으므로 클로저 외부에서 total 값을 변경해도 영향을 준다
- 클로저는 캡처된 변수의 메모리 주소를 참조하기 때문에 동일한 값을 공유한다
일반 함수 내부 함수는 캡처가 아니다
func execute(number: Int) -> Int {
var result = 0
func square(_ n: Int) -> Int {
result += n * n
return result
}
return square(number)
}
execute(number: 3)
execute(number: 4)
execute(number: 5)
- 이 경우 내부 함수
square는 외부로 반환되지 않고 함수 내에서만 사용되므로 캡처가 발생하지 않는다
- 함수가 끝나면 변수
result도 함께 사라진다
- 매번 새로운 함수 실행으로
result는 초기화된다
내부 함수를 반환 → 캡처 발생
func createAdder() -> (Int) -> Int {
var total = 0
func add(_ num: Int) -> Int {
total += num
return total
}
return add
}
let adder = createAdder()
adder(1)
adder(2)
adder(3)
- 함수
createAdder는 내부 함수 add를 반환한다
- 반환 시점에 변수
total이 캡처되어 클로저와 함께 메모리에 남는다
- 이후
adder는 total 값을 누적하며 유지한다
클로저는 참조 타입이다
let copyAdder = adder
copyAdder(4)
adder(5)
adder와 copyAdder는 같은 클로저 인스턴스를 참조한다
- 두 클로저는 동일한
total 값을 공유한다
- 클로저는 참조 타입이기 때문에 변수에 복사해도 동일한 상태를 유지한다
변수에 저장하지 않고 바로 실행하면 캡처되지 않는다
createAdder()(1)
createAdder()(2)
createAdder()(3)
- 클로저를 저장하지 않고 즉시 실행하면 매번 새로운
total이 생성된다
- 따라서 값이 누적되지 않고 항상 초기값부터 시작한다
- 캡처된 값은 클로저가 메모리에 살아있는 동안만 유지된다
정리 - 캡처와 메모리
- 클로저는 주변 변수를 캡처할 수 있다
- 캡처된 변수는 클로저와 함께 Heap 메모리에 저장된다
- 클로저를 변수에 저장하거나 외부로 전달해야 캡처가 유지된다
- 단순히 함수 내에서 실행하고 끝나는 경우 캡처되지 않는다
요약
- 클로저는 외부 변수나 상수를 내부에서 사용할 수 있도록 캡처할 수 있다
- 클로저가 반환되거나 변수에 저장될 때 캡처가 발생한다
- 내부 함수가 외부로 반환되지 않으면 캡처가 일어나지 않는다
- 클로저는 참조 타입으로 복사하더라도 상태를 공유한다
- 저장되지 않고 즉시 실행되는 클로저는 캡처한 값을 유지하지 않는다