본문 바로가기

Swift/RxSwift

[🌱SeSAC] Disposable, Observable, Subject

728x90

Disposable

Disposable은 subscribe 중인(= 구독중인) stream을 원하는 시기에 처리할 수 있도록 도와준다.

 

 

모든 Observable은 Disposable을 반환한다.

이를 통해서 stream을 종료하고 실행되고 있던 시퀀스를 모두 종료한다.

 

 

Observable의 next 이벤트 emit이 끝나면, (= 모든 이벤트가 방출되면)

completed에서 disposed로 시퀀스가 정상적으로 종료된다.

 

하지만, interval 코드 등과 같이 별다른 조건 없이 무한하게 emit 될 수 있는 상황에서는 disposed 되지 않는다.

 

 

클래스의 메모리가 해제되는 시점, 즉 deinit이 되는 시점에서 disposable이 되지만, 만약 (루트 뷰가 변하지 않는 상황에서의) RootViewController일 경우에는 메모리에서 클래스가 해제되지 않는다. 

이 때에는 Root 뷰의 모든 이벤트가 발생되고 나서야 Disposed되거나 아니면 Disposed가 되지 않을 수도 있다.

➡️ 이럴 경우에는 직접 관리를 해주어야 하며, 원하는 시점에 dispose 메서드를 호출해서 리소스를 정리할 수 있다. 

 

그러나, 만약 위와 같은 성격의 코드가 여러 개가 있다면, 여러 개의 이벤트에 대한 구독을 하고 있다면, 모든 구독을 하나씩 종료하는 것은 번거로운 일이다.

그렇기 때문에 DisposeBag의 인스턴스를 초기화하는 과정을 통해서 한번에 리소스를 정리할 수 있다. 

 

 


Observable VS Subject

Observable은 이벤트를 생성하고 전달을 한다.

 

여기서 포인트는 전달만 한다는 것이다.

새로운 값을 Observable에 추가할 수 없다.

 

  1. 사용자의 입력 등에 따라 실시간으로 데이터의 변화를 감지하여 표현하고 
  2. 변경된 데이터를 subscribe를 통해 
  3. emit할 수 있는 객체가 필요하다.

 

Subject는 Observable과 Observer 모두를 담당하고 있으므로 

✅ 이벤트를 전달할 수도 있고

✅ 전달받은 이벤트에 대해서 처리할 수도 있다.

 

 

또한, Observable을 여러 번 subscribe 할 경우 독립적인 실행을 갖지만

subject의 경우는 여러 번 subscribe를 해도 그 실행이 내부적으로 공유된다.

.. 이게 무슨 말인고 .. ? 하니 .. 아래의 코드를 보자.

 

let observableInt = Observable<Int>.create { observer in
    observer.onNext(Int.random(in: 1...100))
    return Disposables.create()
}

observableInt
    .subscribe { value in
        print("observableInt \(value)")
    }
    .disposed(by: disposeBag)

observableInt
    .subscribe { value in
        print("observableInt \(value)")
    }
    .disposed(by: disposeBag)

let subjectInt = BehaviorSubject(value: 0)
subjectInt.onNext(Int.random(in: 1...100))

subjectInt
    .subscribe { value in
        print("subjectInt \(value)")
    }
    .disposed(by: disposeBag)

subjectInt
    .subscribe { value in
        print("subjectInt \(value)")
    }
    .disposed(by: disposeBag)

observable의 경우는 독립적인 실행을 갖기 때문에 각 두번의 실행에서 서로 다른 값이 나오지만

subject의 경우는 두 번의 구독 실행에서 모두 같은 값이 나오는 것을 확인할 수 있다.

 

subject와 observable에 대한 차이는 다른 글에서 좀 더 자세하게 다뤄보자 .. 

 


Subject

Publish Subject

  • 초기값이 없는 빈 상태로 시작한다.
  • 구독 이후 시점, 즉 subscribe 시점 이후부터 emit되는 이벤트를 처리할 수 있다. subscribe 이전에 emit된 이벤트들은 모두 무시된다.
    • 그러므로 구독 이전에 onNext로 여러번 emit해도 이 때 방출한 이벤트는 모두 무시하는 것이다.
let publish = PublishSubject<Int>()

private func publishSubject() {
    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(3)
    publish.onNext(4)
    publish.on(.next(5)) 
    
    publish.onCompleted() 
    
    publish.onNext(6)
    publish.onNext(7)
}

 

위의 코드를 실행하면 아래와 같이 나오는 것을 확인할 수 있다.

구독 전, 다시 말해서 .subscribe를 실행하기 전 publish.onNext(1) 코드와 publish.onNext(2) 코드에 대해서는 emit을 무시한 것이다.

 

Behavior Subject

  • Behavior Subject를 생성할 때 초기값은 필수이다. 
    • 이 점이 Publish Subject와의 차이점이다.
    • 초기값이 있기 때문에 이벤트를 받기 전까지 일종의 더미 데이터 등을 보여줘야 하는 경우, 뷰를 채우기에 용이하다. 
  • Subscribe 이전에 emit한 이벤트가 있다면 가장 최근에 전달된 이벤트 하나를 전달 받을 수 있다.
  • Subscribe 이전에 emit한 이벤트가 없다면 초기값을 전달한다. 

 

쉽게 말해서 Behavior Subject는 subscribe 하기 바로 전 이벤트를 전달 받는다. 

-> 이 때, 바로 전 이벤트가 있을 수도 있고, 없을 수도 있다.

-> 그러므로 초기값이 필요한 것이다.

 

let behavior = BehaviorSubject(value: 100)

func behaviorSubject() {
     behavior.onNext(1)
     behavior.onNext(200)
     
     behavior
         .subscribe { value in
             print("behavior - \(value)")
         } onError: { error in
             print("behavior - \(error)")
         } onCompleted: {
             print("behavior completed")
         } onDisposed: {
             print("behavior disposed")
         }
         .disposed(by: disposeBag)

     behavior.onNext(3)
     behavior.onNext(4)
     behavior.on(.next(5))
     
     behavior.onCompleted()
     
     behavior.onNext(6)
     behavior.onNext(7)
 }

위의 코드를 실행하면 아래의 출력 결과를 얻을 수 있다.

 

1과 200 모두 emit 했지만 구독 시점 바로 직전인 200에 대해서만 이벤트를 전달받고 있다.

 

 

Replay Subject

  • bufferSize에 작성이 된 이벤트만큼 메모리에 이벤트를 갖고 있다가 subscribe를 한 직후 한번에 이벤트를 전달한다. 
    • 이 때 저장된 buffer size만큼을 메모리에서 따로 갖고 있는 것이므로 많은 양을 버퍼로 가질 경우 메모리 부하가 발생할 수 있다. 
  • 오류가 발생하더라도 메모리에서 보유하고 있는 이벤트를 emit하고 error를 notification한다. 

 

let replay = ReplaySubject<Int>.create(bufferSize: 5)

private func replaySubject() {
    replay.onNext(100)
    replay.onNext(200)
    replay.onNext(300)
    replay.onNext(400)
    replay.onNext(500)
    
    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(3)
    replay.onNext(4)
    replay.on(.next(5))
    
    replay.onCompleted()
    
    replay.onNext(6)
    replay.onNext(7)
}

위의 코드를 실행해보면, 5만큼의 메모리를 보유하고 있는 것이다.

그리고 100부터 500까지의 이벤트를 갖고 있다가 구독한 시점 이후에 한번에 emit하는 것이다.

 

만약 이벤트는 그대로 두고 buffer size를 3으로 줄이면?

let replay = ReplaySubject<Int>.create(bufferSize: 3)

 

이렇게 구독 시점 전부터 3개의 이벤트에 대해서만 전달을 받고 있다.

 

 

Async Subject

이 친구는 그렇게 자주 사용하는 친구는 아니라고 하니까 .. 가볍게 알아두기 .. ~

 

  • 위의 세가지 subject는 구독한 이후, 이벤트가 전달되면 즉시 이벤트를 전달한다.
  • 하지만 AsyncSubject는 subscribe를 했더라도 completed 이벤트가 전달 되기 전까지 어떤 이벤트도 전달하지 않는다.
    • 만약 completed 이벤트가 전달되었다면 가장 최근 시점에 전달된 next 이벤트 하나를 함께 전달한다.

 

let async = AsyncSubject<Int>()

private func asyncSubject() {
    `async`.onNext(100)
    `async`.onNext(200)
    `async`.onNext(300)
    `async`.onNext(400)
    `async`.onNext(500)

    `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(3)
    `async`.onNext(4)
    `async`.on(.next(5)) 

    `async`.onCompleted() 

    `async`.onNext(6)
    `async`.onNext(7)
}

위의 코드를 실행하면 

 

이런 결과가 나온다. completed가 되기 직전의 이벤트를 전달한다. (subscribe 직전이 아니라 completed!!)

'Swift > RxSwift' 카테고리의 다른 글

[🌱SeSAC] Observable/Observer, Subject, Relay  (0) 2022.10.26
[🔥Rx뿌셔] Subject VS Observable  (0) 2022.10.26
[🔥Rx뿌셔] Disposable  (0) 2022.10.25
[🔥Rx뿌셔] Subscribe  (0) 2022.10.25
[🔥Rx뿌셔] Observable  (2) 2022.10.24