클로저는 자신이 정의된 주변 환경의 변수나 상수를 캡처하여, 실행 시점에 값들이 존재하지 않더라도 클로저 내부에서 해당 값들에 접근 가능하게 하는 것
func makeIncrementer(by amount: Int) -> () -> Int {
var total = 0
let incrementer: () -> Int = {
total += amount
return total
}
return incrementer
}
let incrementByFive = makeIncrementer(by: 5)
print(incrementByFive()) // 출력: 5
print(incrementByFive()) // 출력: 10
print(incrementByFive()) // 출력: 15
위의 코드처럼 total, amount가 존재하지 않게 되더라도 여전히 동작.
일단 기본적으로 두 타입은 Reference Capture를 한다.
var x = 10
let closure = { print(x) }
x = 20
closure() // 출력: 20
closure 클로저가 x 변수를 참조 캡처하여 x의 값이 바뀌면 그대로 클로저에서도 동일하게 값이 바뀌어 적용된다.
class Counter {
var count = 0
}
let counter = Counter()
let closure = {
print("Count is \(counter.count)")
}
counter.count = 5
closure() // 출력: Count is 5
동일하게 closure가 Counter 객체를 참조 캡처하여 객체의 값을 바꾸면 클로저에도 동일하게 적용된다.
보통 클로저들은 escaping closure와 같이 선언된 곳에서 바로 사용되지 않고 선언된 곳 외부에서 사용되거나 비동기적으로 사용된다.
그 때문에 캡처된 값들은 언제 다시 사용될 지 모르기 때문에 탈출 클로저와 함께 heap에 저장하여 클로저가 실행 될 때 heap에서 값들을 찾아 사용된다.
closure내부에 캡처해오는 값들의 참조 강도를 명시해서 캡처하는 방식
기존의 캡처방식은 모두 Reference Capture
var value1 = 0
var value2 = 0
let closure = { [value1, value2] in
print("value1: \(value1), value2: \(value2)")
}
value1 = 10
value2 = 20
closure() // 출력: value1: 0, value2: 0
closure가 생성될 때 캡처 weak와 unowned로 나뉘어 순환 참조 문제 해결 가능
closure가 실행될 때 캡처class ViewController: UIViewController {
var dataLoader: DataLoader?
func fetchData() {
dataLoader?.loadData(completion: { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let data):
self.updateUI(with: data)
case .failure(let error):
self.showErrorMessage(error)
}
})
}
private func updateUI(with data: Data) {
// UI 업데이트 코드
}
private func showErrorMessage(_ error: Error) {
// 에러 메시지 표시 코드
}
}
기존 self를 캡처하여 참조 횟수가 증가하던 걸 weak로 명시함으로써 ViewController 객체가 사라졌을 때 ARC가 메모리 해제할 수 있게 가능
class DataLoader {
var data = "Initial Data"
func loadData() {
let closure = { [unowned self] in
print("Loading: \(self.data)")
}
closure()
}
}
let loader = DataLoader()
loader.loadData()
unowned 기능과 동일하게 클로저가 실행될 때DataLoader 객체가 존재할 것이라는 가정 하에 안전하게 사용 가능
class ViewController {
var button: UIButton?
func setup() {
let handler = { [weak self, weak button = self.button] in
guard let self = self, let button = button else { return }
self.doSomething()
button.setTitle("Tapped", for: .normal)
}
// handler를 어디선가 사용
}
func doSomething() {
// 어떤 작업 수행
}
}
ViewController 객체 뿐만 아니라 내부의 UIButton객체의 메모리 해제를 위해 weak 명시를 통해 약한 참조 가능
참고
https://stackoverflow.com/questions/40978533/why-swift-closure-not-capture-self
https://skytitan.tistory.com/496
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/
https://www.dhiwise.com/post/swift-capture-lists-solving-real-developer-problems-in-2024