오늘은 Swift Closure의 강한 캡처와 [weak self]의 필요성에 대해 알아보겠습니다.
1. Closure의 강한 캡처
클로저는 외부 변수를 "캡처"하여 내부에서 사용할 수 있는데요.
참조하는 객체를 강하게(strong) 캡처합니다.
이로 인해 다음과 같은 문제가 발생할 수 있는데요.
- 강한 참조 순환(메모리 누수): 만약 클래스의 프로퍼티가 클로저를 가지고 있고, 그 클로저가 self를 캡처한다면,
클래스 인스턴스와 클로저가 서로를 강하게 참조하게 되어 메모리에서 해제되지 않아,
메모리 누수 발생가능 - 의도치 않은 동작: 객체가 이미 소멸되었어야 하는데 클로저에 의해 계속 살아있으면,
예상치 못한 동작이 발생할 수 있습니다.
2. [weak self]의 필요성
이럴 때 필요한 것이, [weak self]인데요.
이는 클로저 내에서,
self를 약한 참조(weak reference)로 캡처하도록 합니다.
이로 인해서, 메모리에서 해제되지 않아 생기는 문제를 방지하는 것이지요.
[weak self]는 다음과 같은 특징을 가집니다.
- 약한 참조: 참조 카운트를 증가시키지 않아, 메모리 관리가 자연스럽게 이루어
- 옵셔널 값: weak 참조된 객체는 옵셔널이 되어, 객체가 메모리에서 해제되면 nil이 됨
3. [weak self] 사용처와 주의할 점
3-1. weak self 사용처
이러한 weak self는 다양한 곳에서 사용될 수 있는데요.
아래와 같은 곳에서 사용할 수 있습니다.
- Combine, RxSwift 등의 스트림: 반응형 프로그래밍에서 사용
- 비동기 작업: 네트워크 요청이나 타이머 같은 비동기 작업의 completion handler
- 싱글톤이나 오래 지속되는 객체의 클로저(생명주기가 긴 객체)
- 델리게이트 콜백: 델리게이트 패턴 구현시
3-2. weak self 사용 시 주의할 점
self를 사용한다고 무조건 weak self를 하는 것은 정답은 아닙니다.
예를 들어, Combine을 이용해서,
Publish와 Subscription(구독)을 구현할 때,
weak self를 클로저 내부에서 사용한다면,
sink클로저가 실행되기도 전에,
약한 참조가 된 self가 메모리에서 해제돼버려서,
구독에 실패하게 될 수 있습니다.
weak self를 사용한 곳에서,
특정 부분의 코드가 실행되지 않거나 할 경우는,
weak self사용으로 인해서,
self가 메모리에서 해제된 것 때문이 아닐지 의심해 볼 필요가 있습니다.
4. 예제
4-1. 예제1
Combine에서 사용하는 [weak self]를 사용하는 코드를 보겠습니다.
class WeatherViewModel {
private var cancellables = Set<AnyCancellable>()
var weatherSubject = PassthroughSubject<Weather, Error>()
func fetchWeather() {
weatherService.fetchCurrentWeather()
.sink { [weak self] completion in
switch completion {
case .finished:
print("날씨 데이터 가져오기 완료")
case .failure(let error):
self?.handleError(error)
}
} receiveValue: { [weak self] weather in
self?.weatherSubject.send(weather)
self?.updateLastFetchedTime()
}
.store(in: &cancellables)
}
...
}
위에서는 WeatherViewModel 클래스의 인스턴스인 self가,
강한 참조를 하는 Closure인 sink 블록에서,
handleError() 함수를 호출하고 있습니다.
이럴 때, [weak self]를 사용해서,
self에 대한 강한 참조로 인한 부작용들을 미연해 방지해 주어야겠지요.
참고로, 위와 같이 combine을 사용할 때는, 항상 store(in: &cancellables)를 사용해야 합니다.
구독을 저장하지 않으면 즉시 메모리에서 해제될 수 있기 때문입니다.
Set<AnyCancellable>에 저장해 놓으면,
객체가 해제될 때 자동으로 구독도 취소됩니다.
4-2. 예제2
마지막으로 예제 하나를 더 보고 마무리하겠습니다.
아래에서도 self를 사용하고 있는데요.
[weak self]로 강한 참조를 방지하였습니다.
class WeatherViewModel: ObservableObject {
private let service = WeatherService()
private var cancellables = Set<AnyCancellable>()
@Published var weather: String = "로딩 중..."
...
func loadWeatherWithWeakSelf() {
print("날씨 로딩 시작 (weak self 사용)")
service.fetchWeather()
.sink { [weak self] weatherData in
guard let self = self else {
print("뷰모델이 이미 해제됨")
return
}
self.weather = weatherData
print("날씨 업데이트 완료 (weak self 사용)")
}
.store(in: &cancellables)
}
...
}
참고로 위에서, 아래와 같이 guard let을 사용하였는데요.
클로저가 복잡하거나 여러 줄일 때,
이 방법은 특히 유용합니다.
만약 self가 nil이면 빠르게 종료하고 빠져나갑니다.
guard let self = self else { return }
참고로 guard let에 관해서는 아래 글을 참조하세요.
>> Swift guard let 이용한 Optional unwrapping 방법
Swift guard let 이용한 Optional unwrapping 방법
오늘은 swift에서 guard let 이용한 Optional unwrapping 방법을 알아보겠습니다. 1. guard let1-1. guard letguard let은 옵셔널 값이 nil이 아닌지 확인하고, nil이 아니라면 옵셔널을 언래핑하는 방법입니
developer88.tistory.com
'iOS, Swift > Swift' 카테고리의 다른 글
Swift enum 총정리 (1) | 2025.03.31 |
---|---|
Swift Parameter Label(매개변수 라벨) as 사용방법 정리 (0) | 2025.03.15 |
Swift inout parameter(파라미터) 함수 마스터하기 (0) | 2025.03.13 |
Swift guard let 이용한 Optional unwrapping 방법 (0) | 2025.03.12 |
Swift 불투명 타입 some은 왜 필요할까? (0) | 2025.03.08 |
댓글