viewModel.$nickname
.sink { value in
self.updateUI(for: value)
}
.store(in: &cancellables)
Combine을 사용하다 보면 클로저 내부에서 self를 캡처하는 경우가 많습니다. 이때 무심코 strong reference로 self를 참조하면 메모리 누수(Retain Cycle)가 발생할 수 있는데요.
viewModel.$nickname
.sink { [weak self] value in
guard let self = self else { return }
self.updateUI(for: value)
}
.store(in: &cancellables)
그렇기에 위와 같이 작성하곤 합니다.
하지만, 매번 반복해야 한다는 점이 번거롭고, 실수할 여지도 많습니다. 특히 여러 스트림을 다루다 보면 코드가 점점 지저분해지고 가독성이 떨어집니다.
RxSwift는 이런 상황을 깔끔하게 처리할 수 있는 withUnretained 연산자를 제공합니다:
viewModel.nickname
.withUnretained(self)
.subscribe(onNext: { owner, value in
owner.updateUI(for: value)
})
이 방식은 클로저에서 self를 약한 참조로 안전하게 사용할 수 있도록 해줍니다. null 체크까지 내부에서 처리되기 때문에 guard 구문도 필요 없고 훨씬 간결하죠.
extension Publisher {
func withUnretained<T: AnyObject>(_ object: T) -> Publishers.CompactMap<Self, (T, Self.Output)> {
compactMap { [weak object] output in
guard let object = object else {
return nil
}
return (object, output)
}
}
}
약한 참조로 순환 참조를 방지하고, guard-let을 이용하여 객체가 살아있는지 확인합니다.
compactMap을 이용하여 객체가 존재하지 않는다면 방출하지 않고, 존재하면 튜플을 반환합니다.
이제 뷰컨트롤러에서 Combine 스트림을 다룰 때 다음처럼 사용할 수 있습니다:
viewModel.$nickname
.withUnretained(self)
.sink { owner, value in
owner.updateUI(for: value)
}
.store(in: &cancellables)