본문 바로가기
iOS, Swift/Swift

Swift Closure의 강한 캡처와 [weak self]의 필요성

by Developer88 2025. 3. 14.
반응형

오늘은 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

 

728x90

댓글