RxSwift의 'withUnretained' 연산자는 메모리 누수를 방지하기 위해 사용되는 연산자이다. 이는 강한 참조로 인해 발생할 수 있는 메모리 누수를 방지하기 위해 'self'에 대한 약한 참조를 사용하여 객체가 더 이상 필요하지 않으면 해제될 수 있도록 한다.
기본적으로 'withUnretained'는 'self'를 약하게 캡처하고, 'self'가 'nil'이 아닐 때에만 연산을 수행한다. 이를 통해 'self'에 대한 강한 참조를 방지하고 메모리 누수를 방지한다.
weak self
private let label = UILabel()
private let button = UIButton(type: .system)
button.rx.tap
.subscribe(onNext: { [weak self] in
guard let self = self else { return }
// 또는 self?. 옵셔널 형식으로 구현
self.label.text = "Button Tapped"
})
.disposed(by: disposeBag)
RxSwift에서 self에 대해 약한 참조를 위해 위와 같이 작성했다.
weak self를 사용하는 경우, 특히 중첩된 클로저가 많아지면 코드가 복잡해지고 가독성이 떨어질 수 있다.
weak self를 대체하기 위해 withUnretained가 RxSwift6 에서 추가 되었다.
withUnretained
private let label = UILabel()
private let button = UIButton(type: .system)
button.rx.tap
.withUnretained(self)
.subscribe(onNext: { owner, _ in
owner.label.text = "Button Tapped"
})
.disposed(by: disposeBag)
withUnretained를 사용하면 코드가 더 간결하고 명확하다. 뿐만 아니라, 'guard let self = self else { return }'을 사용해서 self의 존재를 확인할 필요가 없다.
weak self VS withUnretained
button.rx.tap
.subscribe(onNext: { [weak self] in
guard let self = self else { return }
self.performFirstTask { [weak self] success in
guard let self = self else { return }
if success {
self.performSecondTask { [weak self] result in
guard let self = self else { return }
self.label.text = result
}
}
}
})
.disposed(by: disposeBag)
button.rx.tap
.withUnretained(self)
.subscribe(onNext: { owner, _ in
owner.performFirstTask { [weak owner] success in
guard let owner = owner else { return }
if success {
owner.performSecondTask { [weak owner] result in
guard let owner = owner else { return }
owner.label.text = result
}
}
}
})
.disposed(by: disposeBag)
위는 weak self, 아래는 withUnretained 사용 예제이다.
✅ weak self 사용 예제
1. 'weak self'를 각 클로저에서 반복적으로 사용해야 한다.
2. 각 클로저마다 'guard let self = self else { return }'을 추가해야 한다.
✅ withUnretained 사용 예제
1. 최상위 클로저에서 한 번 'withUnretaind(self)'를 사용해서 'self'를 안전하게 처리한다.
2. owner로 접근해서 코드를 간결하게 유지할 수 있다.
3. 중첩 클로저 내부에서는 'weak owner'를 사용하여 약한 참조를 유지할 수 있다.
4. self가 더 이상 존재하지 않을 때 구독을 자동으로 해제하여 메모리 관리가 더 안전하다.
이와 같은 이유로 'withUnretained'를 사용하면 코드가 더 깔끔하고 가독성이 좋아진다.
subscribe(with:onNext:onError:onCompleted:onDisposed:)
onError, onCompleted, onDisposed에서는 withUnretained가 전달되지 않는다.
내 생각이지만, onError와 onCompleted는 스트림의 종료 이벤트이니까, onNext와 달리 한 번만 발생하고, 특별히 withUnretained를 사용해서 반복적으로 self를 확인할 필요가 없기 때문아닐까?
조금 찾아보니까,
RxSwift의 Event는 열거형인데, error와 completed는 RxSwift 내부에서 정의되어 있기 때문에 변경할 수 없다.
그럼 error와 completed와 disposed에서는 사용 못하나? ➡️ 놉 !
subscribe(with:onNext:onError:onCompleted:onDisposed:)를 사용하면 된다.
뿐만 아니라 bind, driver, emit 등에서도 사용할 수 있다.
생각하기
마지막으로 RxSwift에서 'withUnretained'를 사용하는 이유에 대해 생각해보자.
1. 메모리 누수 방지
2. 가독성 향상
3. 유지 보수성 향상
이번 프로젝트에서 withUnretained를 사용하면서, 확실히 클로저를 직접 다루는 것보다 가독성이 향상되고 코드를 더욱 간결하게 작성할 수 있다는 것을 느꼈다.
역시 가독성이 최고다
'iOS > RxSwift' 카테고리의 다른 글
[RxSwift] UILabel의 탭 이벤트를 감지해보자. (2) | 2024.05.07 |
---|---|
[RxSwift] RxSwift와 RxCocoa로 Toggle Button 구현하기 (0) | 2024.05.02 |
[iOS/RxSwift] RxSwift 완벽 정리 - 2 (0) | 2024.03.07 |
[iOS/RxSwift] RxSwift 완벽 정리 - 1 (1) | 2024.03.07 |
[iOS/RxSwift] Next, Error, Completed 처리하기 (0) | 2024.02.03 |