Observable Lifecycle

  • Create 연산자를 통해 Observable을 생성함(보통 구현되어 있는 Operator를 사용함)
  • Subscribe가 되면 Observable이 실행됨
  • next를 통해 이벤트(제스쳐, 인스턴스 등)을 Emit함.
    • Lifecycle중 오류가 발생하면 error 이벤트를, 정상적으로 모든 이벤트가 완료된다면 completed를 Notification합니다.
    • deinit되는 시점, Sequence가 종료되는 시점에 Dispose가 됩니다.
    • error 또는 completed를 전달 받으면 Sequence가 종료됨 -> 종료된 이후에는 더 이상 이벤트가 발생하지 않습니다. 즉 Observable에 대한 재사용은 불가능함.

Observable & Observer

  • 데이터를 변경해줄 수 있는 이벤트가 있고, 이벤트에 따라서 변경되는 뷰, 로직등이 있습니다.
  • 즉 이벤트를 Emit(방출)하는 Observable이 있고, 이벤트를 처리하는 Observer가 있습니다.
  • Observable과 Observer를 통해 Stream(데이터의 흐름)을 통제할 수 있고, Operator를 통해 Stream을 변경, 조작할 수 있습니다.
  • 하지만 Observable은 Subscrie를 하지 못하기 때문에 이벤트 방출만 할 수 있고, 이벤트에 대한 처리는 하지 못합니다. 
  • Observer 역시 받은 이벤트를 다른 Observer에게 전달하지 못합니다.
  • 그래서 Observer, Observable 두개의 역할을 수행할 수 있는 Subject가 등장하게 됩니다.

Observable Example

 let itemsA = [3.3, 4.0, 5.0, 2.0, 3.6, 4.8]
 let itemsB = [2.3, 2.0, 1.3]
 let disposeBag = DisposeBag()
  • Just
    • Observable 타입 프로토콜의 타입 메서드로 선언이 되어 있음
    • element Parameter로 하나의 값을 받아 Observable을 Return함. 즉 하나의 값만 Emit함
Observable.just(itemsA)
            .subscribe { value in
                print("just - \(value)")
            } onError: { error in
                print("just - \(error)")
            } onCompleted: {
                print("just completed")
            } onDisposed: {
                print("just disposed")
            }
            .disposed(by: disposeBag)
  • 반환값

 

  • Of
    • Observable 타입 프로토콜의 타입 메서드로 선언이 되어 있음
    • element Parameter가 가변 파라미터로 선언되어 있어 여러 가지의 값을 동시에 전달할 수 있음.
    • 즉 두개 이상의 값을 emit할 수 있음.
Observable.of(itemsA, itemsB)
            .subscribe { value in
                print("of - \(value)")
            } onError: { error in
                print("of - \(error)")
            } onCompleted: {
                print("of completed")
            } onDisposed: {
                print("of disposed")
            }
            .disposed(by: disposeBag)
  • 반환값

 

 

  • From
    • 배열의 각 요소를 Emit하고 싶다면 from을 사용함
    • array Parameter로 배열을 받고, 배열의 각 element를 Observable로 리턴하고 있음
Observable.from(itemsA)
            .subscribe { value in
                print("from - \(value)")
            } onError: { error in
                print("from - \(error)")
            } onCompleted: {
                print("from completed")
            } onDisposed: {
                print("from disposed")
            }
            .disposed(by: disposeBag)
  • 반환값

 

  • combineLatest
    • 2개 이상의 항목을 결합하여 Observable로 Emit함.
    • 하나의 항목에만 이벤트가 발생하더라도 이벤트를 발생시킴

 

  • take
    • 방출된 아이템 중 처음 n개의 아이템을 내보냄.
// repeatElement : 반복
        Observable.repeatElement("MosonLEE")
            .take(5) // 최대 5번까지 시도
            .subscribe { value in
                print("repeat - \(value)")
            } onError: { error in
                print("repeat - \(error)")
            } onCompleted: {
                print("repeat completed")
            } onDisposed: {
                print("repeat disposed")
            }
            .disposed(by: disposeBag)
  • 반환값


Observable vs Subject

  • Observable
    • Observable은 이벤트를 생성하고 전달합니다.
    • 새로운 값을 Observable에 추가를 할 수 없습니다.
    • 사용자의 입력 등에 따라 실시간으로 데이터의 변화를 감지하여 표현하고 변경된 데이트를 Subscribe를 통해 emit할 수 있는 객체가 필요합니다.
  • Subject
    • Subject은 Observable과 Observer를 모두 담당하고 있기 때문에, 이벤트를 전달할 수도 있고 전달받은 이벤트에 대해서 처리할 수도 있습니다.
    • 또한 Observable을 여러번 Subscribe 할 경우 독립적인 실행을 갖지만, Subject의 경우 여러 번 Subscribe 하더라도 Observable의 실행이 내부적으로 공유됩니다.
    • Subject에는 4개의 종류가 있습니다.
      • PublishSubject
      • BehaviorSubject
      • ReplaySubject
      • AsyncSubject

Subject Example

  • PublishSubject
    • 초기값이 없는 빈 상태로 시작합니다.
    • subscribe 이후 시점부터 emit 되는 이벤트를 처리할 수 있는 특성을 가지고 있습니다.
    • subscribe 이전에 emit된 이벤트들은 무시됩니다.
//publish
        // observable, observer의 역할을 동시에 수행
        // subscribe 전/Error/ completed notification 이후 이벤트 무시
        // subscribe 후에 대한 이벤트는 다 처리
        let publish = PublishSubject<Int>() //초기값이 없는 빈 상태
        publish.onNext(1)
        publish.onNext(2)
        
        publish
            .subscribe {value in
                print("publish - \(value)")
            } onError: { error in
                print("publish - \(error)")
            } onCompleted: {
                print("publish completed")
            } onDisposed: {
                print("publish disposed")
            }
            .disposed(by: disposeBag)
        
        publish.onNext(10)
        publish.onNext(11)
        publish.on(.next(12))
        
        publish.onCompleted()
        
        //실행 안됨
        publish.onNext(15)
        publish.onNext(16)
  • 반환값

 

  • BehaviorSubject
    • BehaviorSubject를 생성할 때 초기값은 필수이다. (PublishSubject와의 차이점)
    • Subscribe 이전에 emit한 이벤트가 있다면, 가장 최근에 전달된 이벤트 하나를 전달받을 수 있습니다.
    • Subscribe 이전에 emit한 이벤트가 없다면 초기값을 전달합니다.
    • 뷰를 미리 채워두기 용이합니다.
 behaivor.onNext(1)
        behaivor.onNext(2)
        //구독하기 전 가장 최신의 값을 가져옴
        behaivor
            .subscribe {value in
                print("behaivor - \(value)")
            } onError: { error in
                print("behaivor - \(error)")
            } onCompleted: {
                print("behaivor completed")
            } onDisposed: {
                print("behaivor disposed")
            }
            .disposed(by: disposeBag)
        
        behaivor.onNext(10)
        behaivor.onNext(11)
        behaivor.on(.next(12))
        
        behaivor.onCompleted()
        
        //실행 안됨
        behaivor.onNext(15)
        behaivor.onNext(16)
  • 반환값

 

  • ReplaySubject
    • bufferSize에 작성이 된 이벤트만큼, 메모리에 이벤트를 가지고 있다가 subsribe를 한 직후에 한번에 이벤트를 전달합니다.
    • 많은 양을 버퍼로 가지고 있을 때는 메모리 부하가 발생할 수 있습니다.
//BufferSize 메모리, array, 이미지
        replay.onNext(1)
        replay.onNext(2)
        replay.onNext(100)
        replay
            .subscribe {value in
                print("replay - \(value)")
            } onError: { error in
                print("replay - \(error)")
            } onCompleted: {
                print("replay completed")
            } onDisposed: {
                print("replay disposed")
            }
            .disposed(by: disposeBag)
        
        replay.onNext(10)
        replay.onNext(11)
        replay.on(.next(12))
        
        replay.onCompleted()
        
        //실행 안됨
        replay.onNext(15)
        replay.onNext(16)
  • 반환값

 

  • AsyncSubject
    • PublishSubject, BehaviorSubject, ReplaySubjects는 subscribe한 이후 이벤트가 전달되면 즉시 이벤트를 전달합니다.
    • 하지만 AsyncSubject는 subscribe을 했더라도 completed 이벤트가 전달이 되기 전까지 어떤 이벤트도 전달하지 않습니다.
      • 만약 completed 이벤트가 전달되었다면, 가장 최근 시점에 전달된 next 이벤트 하나를 함께 전달합니다.
//마지막 하나만 넣어줌
        async.onNext(1)
        async.onNext(2)
        async.onNext(100)
        async
            .subscribe {value in
                print("async - \(value)")
            } onError: { error in
                print("async - \(error)")
            } onCompleted: {
                print("async completed")
            } onDisposed: {
                print("async disposed")
            }
            .disposed(by: disposeBag)
        
        async.onNext(10)
        async.onNext(11)
        async.on(.next(12))
        
        async.onCompleted()
        
        //실행 안됨
        async.onNext(15)
        async.onNext(16)
  • 반환값


Relay

  • Relay는 PublishRelay, BehaviorRelay 2가지가 있고 Subject와 거의 유사한 특성을 가지고 있습니다.
  • Subject와 Relay의 가장 큰 차이는 Relay의 경우 Completed Error 이벤트를 받지 못한다는 것입니다.
  • UI가 Error와 Completed 이벤트를 받게 된다면 더 이상 next 이벤트를 전달 받을 수 없게 되고 자연스럽게 반응형의 장점이 사라지기 때문입니다.
  • Relay는 Completed Error 이벤트를 받지 못하기에 disposed 되기 전까지 Subscribe가 해제되지 않습니다.
    • completed or error를 받고 나면 disposed 상태가 되는데 받지 못하니 disposed가 호출되는 deinit시점에 직접 처리를 해주어야함
  • 그래서 Relay는 next 이벤트만 처리합니다.
  • next 이벤트를 aceept이라는 키워드로 사용합니다.
  • 요약해보자면 subject는 Observable, relay는 driver와 자주 사용이됩니다.


Subscribe -> Bind -> Drive

  • Observable을 subscribe합니다.
  • Background Schedular에서 동작할 수 있는 가능성(네트워크 통신, 파일 다운로드 등)이 있기 때문에 Observable의 데이터 흐름을 MainSchedular(메인 쓰레드)에서 동작할 수 있도록 변경해줍니다.
 // 네트워크 통신이나 파일 다운로드 등 백그라운드 작업
button.rx.tap
            .observe(on: MainScheduler.instance) //다른 쓰레드로 동작하게 변경
            .withUnretained(self)
            .subscribe { (vc, _) in
                vc.label.text = "안녕 반가워"
            }
            .disposed(by: disposeBag)
  • subscribe와 유사하지만 MainSchedular의 동작과 error 이벤트를 방출하지 않는 특성을 발ㅇ휘할 수 있는 bind를 통해 값을 주입해줍니다.
// bind: subsribe, mainschedular error X
        // 무조건 메인 쓰레드에서 작동
        button.rx.tap
            .withUnretained(self)
            .bind { (vc, _) in
                vc.label.text = "안녕 반가워"
            }
            .disposed(by: disposeBag)
  • tap의 ControlEvent<Void>를 map Operator를 통해 데이터의 흐름을 조작해줍니다.
  • 그럼 String 타입으로 변경이 가능해져서 label.rx.text로 간단하게 bind를 해줄 수 있습니다.
 // operator로 데이터의 stream 조작
        button.rx.tap
            .map{ "안녕 반가워"}
            .bind(to: label.rx.text)
            .disposed(by: disposeBag)
  • Stream이 공유될 수 있는 특성인 Driver Traits를 활용합니다.
// driver traits: bind + stream 공유(리소스 낭비 방지, share())
        // Relay와 driver는 연관됐음
        button.rx.tap
            .map { "안녕 반가워" }
            .asDriver(onErrorJustReturn: "")
            .drive(label.rx.text)
            .disposed(by: disposeBag)

 


Traits & Driver

  • UI 처리에 특화된 Observable을 Trait이라 부르고, RxSwift에서 제공해주는 Observable, RxCocoa에서 제공해주는 Observable이 있습니다.
  • Traits에 해당하는 Observable의 공통적인 특성은 Main Thread에서 실행된다는 점입니다.
  • 또한 Error 이벤트도 없습니다.
  • Traits 중 Signal을 제외하고는 share 연산자가 내부적으로 사용되고 있기 때문에 Subscribe를 하게 될 경우 동일한 sequence를 공유하게 됩니다.
  • Rxswift
    • Single
    • Completable
    • Maybe
  • RxCocoa
    • Driver
    • Signal
    • ControlProperty/ControlEvent
  • Driver
    • Main Thread에서의 실행을 보장해줍니다.
    • Observable이 UI로 특화된 형태이기 때문에 subscribe만 할 수 있고 값을 변경할 수는 없습니다.
    • bind와 달리 Stream 공유가 됩니다.
      • bind는 subscribe의 별칭입니다.
      • driver는 내부적으로 share(replay: 1, scope: .wholeConnected)가 구현되어 있습니다.
  • Share
    • 일반적으로 subscribe를 할 때마다 새로운 시퀀스가 생성이 됩니다. 즉 하나의 Observable을 subscribe(bind)하는 곳이 여러 곳이라면 그만큼 호출이 되면서 스트림이 생기게됩니다.
    • 따라서 여러 subscribe를 하게 될 경우, 불필요한 리소스가 발생할 수 있기 때문에 내 부적으로 모든 subscribe가 한의 subscribe를 공유할 수 있도록 해야합니다.
    • replay와 scope를 통해 버퍼 사이즈와 유지 상태를 결정할 수 있습니다.
      • share()가 구현이 되어 있지 않다면 네트워크 요청이 여러번 일어나게 됩니다.
      • 따라서 불필요한 콜이나 리소스 낭비가 생기지 않도록 subscribe를 공유할 수 있게 처리해야합니다.

Scheduler

  • iOS의 GCD와 유사하며, Thread를 관리를 하기 위해 Scheduler를 사용합니다.
  • Serial Scheduler, Concurrent Scheduler, Custom Schedular 등이 있습니다.
  • Scheduler의 연산자는 observeOn과 subscribeOn이 있습니다.
    • observeOn
      • Observable이 Observer에게 다른 Thread로 동작하게 변경합니다.
      • observeOn 이후에 오는 연산자에 대한 Scheduler를 지정하게 됩니다.
      • observeOn은 특정 작업이나 연산에 대한 scheduler를 변경할 수 있어 여러 번 사용할 수 있습니다.
      • ex) observe(on: MainScheduler.instance) - Main Thread로 변경
    • subscribeOn
      • Observable이 시작이 되는 Scheduler를 지정합니다.
      • subscribeOn은 Observable이 동작하는 Scheduler를 변경하는 것이기 때문에 한번만 사용하는 것이 좋습니다.
  • subsribe를 사용할 경우 네트워크 통신 작업 등이 발생하면 Background Scheduler 상태로 동작할 수 있습니다.
  • UI와 관련된 코드가 들어가 있을 경우 오류가 발생합니다.

Diposable

  • Disposable은 Subscribe중인 Stream을 원하는 시기에 처리할 수 있도록 도와줌.
  • Observable은 모두 Disposable을 Return함. 이를 통해 Stream을 종료하고 실행되던 Sequence를 모두 종료함.
  • Observable의 next 이벤트에 대한 emit이 끝나면, completed -> disposed로 Sequence가 정상적으로 종료됨.
  • 하지만 next 이벤트가 무한히 emit될 수 있는 상황에서는 dispose가 되지 않음
  • 클래스이 메모리가 해제되는 시점(deinit이 되는 시점)에 Disposable이 되지만 RootVC일 경우에는 메모리에서 클래스가 해제되지 않습니다.
  • 따라서 끝까지 이벤트를 진행하고 Disposed 되거나 Disposed 되지 않을 수도 있습니다.
  • 이런 케이스에선 직접 관리를 해주어야 하며, 원하는 시점에 dispose 메서드를 호출해서 리소스를 정리해줄 수 있습니다.
  • 하지만 여러개를 구독하고 있다면, 일일 이 모든 구독을 종료하는 것은 힘들기 때문에 DisposeBag의 인스턴스를 초기화하는 과정을 통해 한번에 리소스를 정리할 수 있습니다.
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
	//intervalObservable.dispose() // 5초 후에 dispose
	self.disposeBag = DisposeBag()
}

 

+ Recent posts