오늘은 Swift의 클로저와 캡처리스트에 대해 이야기해보려 합니다. 특히 클로저에서 값 타입과 참조 타입의 캡처 방식과 순환 참조 문제를 해결하는 캡처리스트의 중요성에 대해 집중적으로 다루겠습니다.
클로저의 기본 캡처 방식
Swift에서 클로저는 기본적으로 외부의 변수를 캡처할 때 참조 타입으로 캡처합니다. 즉, 값 타입이든 참조 타입이든 상관없이 클로저 내부에서는 모두 참조로 취급됩니다. 이는 매우 편리한 기능이지만, 때로는 메모리 관리나 예상치 못한 동작을 야기할 수 있는 문제점을 내포하고 있습니다.
캡처리스트와 값 타입의 복사
클로저에서 캡처리스트를 사용하면, 이야기가 조금 달라집니다. 캡처리스트를 사용하면 값 타입을 복사해서 가져올 수 있습니다. 이는 값 타입이 원래 값을 유지하면서 클로저 내부에서 안전하게 사용될 수 있도록 해줍니다.
캡처리스트를 사용하지 않는 경우
먼저 캡처리스트를 사용하지 않는 경우를 살펴봅시다.
var number = 42
let closure = {
print(number)
}
number = 0
closure() // 0을 출력
위 코드에서 closure
는 외부의 number
를 참조합니다. closure
가 실행되는 시점에 number
의 값이 0으로 변경되었기 때문에, 클로저 내부에서 0을 출력하게 됩니다.
캡처리스트를 사용하는 경우
이번에는 캡처리스트를 사용해 보겠습니다.
var number = 42
let closure = { [number] in
print(number)
}
number = 0
closure() // 42를 출력
캡처리스트 [number]
를 사용하는 경우, 클로저가 생성될 때의 number
값인 42가 복사되어 클로저 내부에서 사용됩니다. 따라서 외부에서 number
의 값이 변경되어도, 클로저 내부에서는 복사된 원래 값인 42를 출력하게 됩니다. 따라서 외부에서 number
값이 변경되어도 클로저 내부의 값에는 영향을 주지 않습니다.
참조 타입과 순환 참조 문제
앞서 말했듯이 값 타입의 경우 기본 캡처 기능과 달리 값을 그대로 복사해 오지만, 참조 타입의 경우는 똑같이 참조합니다. 이렇게 보면 참조 타입에는 캡처리스트를 쓸 일이 없을 거 같은데, 정말 그럴까요? 그렇지 않습니다.
참조 타입을 캡처리스트에 사용하면, 참조 방식을 직접 정해줄 수 있습니다. 기본적으로 참조 타입을 캡처할 때는 강한 참조(Strong Reference)가 기본값입니다.
그렇기에 이런 기본적인 참조 방식 때문에 순환 참조(Strong Reference Cycle)라는 문제가 발생할 수 있습니다. 이는 클래스 인스턴스와 클로저가 서로를 강하게 참조하여 메모리에서 해제되지 못하는 문제를 의미합니다.
하지만 캡처리스트를 활용하면, weak
또는 unowned
키워드를 사용하여 순환 참조 문제를 방지할 수 있습니다.
캡처리스트를 사용하지 않는 경우
먼저 캡처리스트를 사용하지 않는 경우를 살펴봅시다.
class MyClass {
var closure: (() -> Void)?
var value = 10
deinit {
print("MyClass 인스턴스 해제")
}
}
var instance: MyClass? = MyClass()
instance?.closure = {
print(instance?.value ?? 0)
}
// 참조 카운트: instance(1), closure(1)
instance = nil
// 참조 카운트: instance(0), closure(1)
// MyClass 인스턴스가 해제되지 않음
위 코드에서 closure
가 참조 타입인 instance
를 캡처 함으로서 instance
를 참조하게 되고 그로 인해 instance
를 해제하기 위해 nil
로 설정해도 closure
가 아직 살아 있기에 메모리에서 해제되지 않고 순환참조 문제가 발생하게 됩니다.
캡처리스트를 사용하는 경우
이제 캡처리스트를 사용한 예시를 살펴보겠습니다.
class MyClass {
var closure: (() -> Void)?
var value = 10
deinit {
print("MyClass 인스턴스 해제")
}
}
var instance: MyClass? = MyClass()
instance?.closure = { [weak instance] in
print(instance?.value ?? 0)
}
// 참조 카운트: instance(1)
instance = nil
// 참조 카운트: instance(0)
// MyClass 인스턴스가 정상적으로 해제됨
[weak instance]
캡처리스트를 사용함으로써, closure
는 instance
를 약한 참조로 캡처합니다. 이렇게 되면, instance
를 nil
로 설정할 때 인스턴스가 정상적으로 메모리에서 해제됩니다.
이를 통해 순환 참조 문제를 방지할 수 있습니다.
이것으로 캡처리스트에 대해 알아보았습니다. 이번 포스트가 여러분들에게 도움이 되었길 바라며, 언제든지 질문이 있으시다면 댓글로 남겨주세요. 다음 포스트에서 뵙겠습니다. 감사합니다!
'Swift' 카테고리의 다른 글
[Swift] iOS FontParser 경고 원인과 해결법 (SDK에 폰트 포함된 경우) (1) | 2025.05.01 |
---|---|
[Swift] mutating 키워드 알아보기 (0) | 2023.08.24 |
[Swift] 클로저와 NotificationCenter를 활용한 상태 변화 알리기(View Controller 간의 상호작용) (0) | 2023.07.31 |
[Swift] 'textView(_:shouldChangeTextIn:replacementText:)' 메서드 두 번 호출 버그 (0) | 2023.07.25 |
[Swift] 메모리 관리: 미소유 참조(Unowned References) (0) | 2023.07.21 |