관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] RxSwift의 deferred 이해하기 본문

iOS 응용 (swift)

[iOS - swift] RxSwift의 deferred 이해하기

jake-kim 2023. 1. 12. 22:42

RxSwift의 Deferred

  • 단어 그대로, deferred 함수에는 파라미터로, Observable을 리턴하는 클로저를 받아서 그 클로저를 내부적으로 저장해놓았다가 해당 Observable이 필요할 때 그 클로저에 접근하여 사용
  • Observable을 지연시키는 것이 아닌, deferred의 인수로 들어간 클로저안의 동작을 지연시키는 것임을 주의
extension ObservableType {
    public static func deferred(_ observableFactory: @escaping () throws -> Observable<Element>)
        -> Observable<Element> {
        Deferred(observableFactory: observableFactory)
    }
}

ex) Observable을 리턴하는 메소드 준비

private func firstObservable() -> Observable<Void> {
    .create { [weak self] observer in
        observer.onNext(())
        observer.onCompleted() // concat은 onCompleted 방출해야 아래 스트림이 동작
        return Disposables.create()
    }
}

private func secondObservable() -> Observable<Void> {
    .create { [weak self] observer in
        observer.onNext(())
        observer.onCompleted()
        return Disposables.create()
    }
}
  • 아래 1)번과 2번 동작 차이는 없는것을 주의 
    • 주의) concat은 onCompleted()가 방출될때 아래 스트림 동작하므로 위 observable에서 onCompleted() 실행 필수
    • deferred를 사용하면 secondObservable() 메소드 호출이 지연되지만, secondObservable()의 메소드에는 Observable을 리턴하는 메소드밖에 없으므로 deferred가 없이 호출하는 상태(= 위에서 아래로 Observable 스트림 수행)와 동일
// 1)
Observable
    .concat(
        firstObservable(),
        secondObservable()
    )
    .subscribe()
    .disposed(by: disposeBag)

// 2)
Observable
    .concat(
        firstObservable(),
        .deferred { [weak self] in self?.secondObservable() ?? .empty() }
    )
    .subscribe()
    .disposed(by: disposeBag)
  • Observable을 지연시키는 것이 아닌, deferred의 인수로 들어간 클로저안의 동작을 지연시키는 것임을 이해할 것

ex) 전역으로 someProperty를 두고, nil값을 가지며 concat에서 상위 스트림인 firstObservable()에서 초기화를 한 후 하위 스트림인 secondObservable()에서 강제 unwrpping해도 크래시가 나지 않으며 정상 초기화 될 것을 확인

class ViewController: UIViewController {
    var someProperty: Int?
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        Observable
            .concat(
                firstObservable(),
                secondObservable()
            )
            .subscribe()
            .disposed(by: disposeBag)
        
    }
    
    private func firstObservable() -> Observable<Void> {
        .create { [weak self] observer in
            print("first")
            self?.someProperty = 1
            observer.onNext(())
            observer.onCompleted()
            return Disposables.create()
        }
    }
    
    private func secondObservable() -> Observable<Void> {
        .create { [weak self] observer in
            print("second")
            print(self?.someProperty!)
            observer.onNext(())
            observer.onCompleted()
            return Disposables.create()
        }
    }
}

ex) 만약 deffered를 사용하지 않고 secondObservable()에 접근하면 아래처럼 Observable이 아닌 밖에서 someProperty 강제 unwrapping하는 경우 크래시 발생

  • deferred를 사용하지 않으면 상위 Observable이 끝나기 전에, someProperty!에 접근하게 되므로 크래시 발생
Observable
    .concat(
        firstObservable(),
        secondObservable()
    )
    .subscribe()
    .disposed(by: disposeBag)

private func secondObservable() -> Observable<Void> {
    print("second")
    print(someProperty!) // crash!
    return .just(())
}
  • 이 때 사용하는 쪽에서 deferred로 접근하면 secondObservable()의 body 부분 접근까지 지연되어서 크래시 해결이 가능
Observable
    .concat(
        firstObservable(),
        .deferred { [weak self] in self?.secondObservable() ?? .empty() } // <-
    )
    .subscribe()
    .disposed(by: disposeBag)

private func secondObservable() -> Observable<Void> {
    print("second")
    print(someProperty!) // no crash
    return .just(())
}
  • 결론
    • deferred 연산자는 Observable을 지연시키는 것이 아닌, deffered 안에 클로저로 주입한 해당 block 자체를 지연시키는 것

* 전체 코드: https://github.com/JK0369/ExDefferedObservable

Comments