RxSwift - 알아보기 Subject

JSLee·2022년 2월 22일
1

Subject

subjectObservableType Protocol을 채택 하고 있는 Observable상속하고 있으며,
ObserverType Protocol 을 채택하고 있습니다.

Run Time 시에 Observable 에 값을 추가하여 방출(Emit)이 발생하게 하는 대리자 입니다.
종합적으로 보면 Observable 과 Observer 의 기능들을 수행합니다.
하지만 Subject 는 구독자(subscriber) 가 아니란것을 주의 해야 합니다.

위 이미지 에서 볼수 있듯이
Observable 은 하나의 Observer 만 subscribe 할수 있습니다.

하지만 Subject 는 multicast 방식 이기 때문에 여러개의 Observer 가 subscribe 할수 있습니다.

  • Observable : unicast 방식
  • Subject : multicast 방식

여기서 Observable 에서 다시 한번 생각 해보면 Observable 은 단지 하나의 Method 라고 생각할수 있기에 어떤 상태도 가지지 않습니다.

그렇기에 새로운 Observer 가 Observable 을 관찰 가능하게 create 해 주어야 합니다.

RxSwift 의
Observable 은 subscribe 된 observer 가 Observable 에 대해 독립적인 실행을 갖습니다.

let observable = Observable<Int>.create{ observer in
                observer.onNext(Int.random(in: 0 ..< 100))
                return Disposables.create()
            }
            
        observable.subscribe(onNext: { (element) in
                print("observer 1 : \(element)")
        }).disposed(by: disposBag)
        observable.subscribe(onNext: { (element) in
                print("observer 2 : \(element)")
        }).disposed(by: disposBag)
        
        
observer 1 : 81
observer 2 : 7

Observable 은 Random 정수를 다른 값으로 방출 합니다.

하나의 Observable 을 subscribe 하는 두개의 Observer 들 이라도 Observable 은 각각 실행됩니다.

이것이 Observable 의 cast 방식인 unicast 방식입니다.

Error 와 Completed 동작

        let observable = Observable<Int>.create { observer in
            observer.onNext(Int.random(in: 0 ..< 100))
            observer.onError(ObservableError.observableError)
            return Disposables.create()
        }
        let firstObservable = observable.subscribe { print("첫번째 Observer === \($0)")}
        firstObservable.disposed(by: disposBag)
        
        let secondObservable = observable.subscribe { print("두번째 Observer === \($0)")}
        secondObservable.disposed(by: disposBag)
        /*
         첫번째 Observer === next(60)
         첫번째 Observer === error(observableError)
         두번째 Observer === next(33)
         두번째 Observer === error(observableError)
         */

Error 와 Completed 의 진행은 같습니다.


subject 반응

 let behaSub = BehaviorSubject(value: 0)
        behaSub.onNext(Int.random(in: 0..<100))
           
        behaSub.subscribe(onNext: { (element) in
               print("observer subject 1 : \(element)")
        }).disposed(by: disposBag)
        behaSub.subscribe(onNext: { (element) in
               print("observer subject 2 : \(element)")
        }).disposed(by: disposBag)
    
    
observer subject 1 : 92
observer subject 2 : 92

print를 보게 되면 subject 에서 subscribe 가 이루어지면 Event 가 전달 되는 것은 같은 값으로 사용됩니다.

그럼 간단하게 Observable 과 Subject 에 대한 차이점에 대해 이야기 할수 있습니다.

  • Observable 은 하나의 Observer 에 대한 간단한 Observable 이 필요할때

  • Subject 는 자주 데이터를 저장하고 수정 , 여러개의 Observer 가 데이터를 관찰해야 할때

        let pubSub = PublishSubject<Int>()
        pubSub.onNext(Int.random(in: 0 ..< 10 ))
        
        pubSub.subscribe(onNext: {
            print($0)
        }).disposed(by: disposBag)
        
        pubSub.onNext(Int.random(in: 11 ..< 20))
        
        pubSub.subscribe(onNext:  {
            print($0)
        }).disposed(by: disposBag)
        
        pubSub.onNext(Int.random(in: 21 ..< 30))
        
        
// print 
// subscribe 이전 onNext 는 무시
19 // 첫번째 subscribe
25 // 첫번째 subscribe
25 // 두번째 subscribe

PublishSubject 또한 마찬가지로 반응 합니다.

subscribe 되기전 Event 에 대해선 무시하지만

subscribe 가 이루어 진뒤 일어나는 Event 에 대해선 모든 공유하게 됩니다.

그럼 Subject의 종류들을 살펴보겠습니다.

PublishSubject

  • 첫번째 : PublishSubject
  • 두,세 번째 : 구독자(subscribers)
  • 위로 올라가는 화살표 : 구독
  • 아래로 내려오는 화살표 : 방출

두번째 줄이 구독 하였을때 1 은 받지 못한다. 하지만 2 와 3 은 받을수 있다.
세번째 줄이 구독 하였을때 1,2 은 받지 못한다. 하지만 3 은 받을수 있다.

PublishSubject 는 구독(subscribe) 되기 전에 Emit(방출) 되는 모든 Event 들을 무시합니다.
그이후 구독(subscribe) 이 이루어진후 Event 만 방출 합니다.

또한 publishSubject 는 empty 의 상태로 시작합니다.

    let subject = PublishSubject<String>()
        subject.onNext("첫번째 넥스트")
        subject.subscribe(onNext : {
            print($0)
        }).disposed(by: disposeBag)
        subject.onNext("두번째 넥스트")
        subject.onNext("세번째 넥스트")
        
        //print
        두번째 넥스트
        세번째 넥스트

앞써 설명하였듯이
PublishSubject 는 Muticast 방식이고 하나의 Subject 로 여러 Observer 와 공유 할수 있습니다.

하지만 onCompleted , onError 가 발생하였을 경우는 어떻게 될지 알아 보겠습니다.

        let subject = PublishSubject<String>()
        
        subject.onNext("첫번째 onNext === 1")
        
        let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
            firstObserver.disposed(by: disposBag)
        
        subject.onNext("두번째 onNext : === 2")
        
        let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
        secondObserver.disposed(by: disposBag)
        
        subject.onNext("세번째 onNext : === 3")
        
        
        //print
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)

subject 의 기본 동작 입니다.

이렇게 subscribe 하는 이후 시점부터 event 를 전달받는
observer 들이 존재합니다 . 여기서 세번째 onNext 이후 completed 가 진행되면

        let subject = PublishSubject<String>()
        
        subject.onNext("첫번째 onNext === 1")
        
        let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
            firstObserver.disposed(by: disposBag)
        
        subject.onNext("두번째 onNext : === 2")
        
        let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
        secondObserver.disposed(by: disposBag)
        
        subject.onNext("세번째 onNext : === 3")
        
        subject.onCompleted()
 //print       
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)
첫번째 observer === completed
두번째 observer === completed       
        

모든 Observer 는 completed 를 전달 받게 됩니다.

여기에 새로운 observer 를 추가해보겠습니다.

        let subject = PublishSubject<String>()
        
        subject.onNext("첫번째 onNext === 1")
        
        let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
            firstObserver.disposed(by: disposBag)
        
        subject.onNext("두번째 onNext : === 2")
        
        let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
        secondObserver.disposed(by: disposBag)
        
        subject.onNext("세번째 onNext : === 3")
        
        subject.onCompleted()
        
        let thirdObserver = subject.subscribe { print("세번째 observer === \($0)")}
        thirdObserver.disposed(by: disposBag)
        
//print
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)
첫번째 observer === completed
두번째 observer === completed
세번째 observer === completed     

PublishSubject 를 subscribe 하는 observer 는 구독 전 Event 에 대한 값을 받지 못합니다.

하지만 completed 가 일어난뒤 구독하는 observer 들은 더이상의 onNext 를 할수 없기에 completed 를 전달 받습니다.

실제 completed 가 일어난뒤

        subject.onNext("네번째 onNext : === 4")

해 보아도 subject는 Event 를 방출하지 않습니다.

그렇다면 Error Event 를 전달하게 된다면 observer 는 어떤 반응을 할지 알아보겠습니다.

   enum SubjectError : Error {
            case subjectError
        }
        let subject = PublishSubject<String>()
        
        subject.onNext("첫번째 onNext === 1")
        
        let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
            firstObserver.disposed(by: disposBag)
        
        subject.onNext("두번째 onNext : === 2")
        
        let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
        secondObserver.disposed(by: disposBag)
        
        subject.onNext("세번째 onNext : === 3")
        
        //subject.onCompleted()
        subject.onError(SubjectError.subjectError)
        
        let thirdObserver = subject.subscribe { print("세번째 observer === \($0)")}
        thirdObserver.disposed(by: disposBag)
        
        subject.onNext("네번째 onNext : === 4")
        //print
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)
첫번째 observer === error(subjectError)
두번째 observer === error(subjectError)
세번째 observer === error(subjectError)

completed 와 마찬가지고 Error 가 발생되는 시점부터 어떠한 Event 도 받을수 없게 됩니다.

적재적소:
시간의 흐름과 연관된 모델에 적합하다.
쇼핑몰 세일 이벤트 의 경우 11시까지 진행이면 그 전에 App에 log 한 고객들 에겐 배너를 보여줄 필요가 있지만 이후 log 한 고객들에겐 그 배너를 보여줄 필요가 없다.

BehaviorSubject

BehaviorSubject 는 생성시점에 초기값을 구현하고 구독시 최신 이벤트를 저장해두었다가 전달합니다.
PublishSubject와 Subject로서의 기능을 동일합니다. 하지만 초기값을 설정한다는 점과 최신 이벤트를 저장해두고 구독시 전달한다는 점이 다릅니다.

  • 첫번째 : BehaviorSubject
  • 두,세 번째 : 구독자(subscribers)
  • 위로 올라가는 화살표 : 구독
  • 아래로 내려오는 화살표 : 방출

PublishSubject 는 받지 못하지만 BehaviorSubject 은 구독 전 최신 이벤트를 저장해 주기 때문에 구독전 이벤트를 사용할수 있습니다.

        let subject = BehaviorSubject<String>(value: "초기값")
        // 구독전 Event 없음
        let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
        firstObserver.disposed(by: disposBag)
        subject.onNext("첫번째 onNext")
        
        // seoncdObserver 는 최신값이 초기값이 아니기때문에 초기값을 받을수 없다.
        let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
        secondObserver.disposed(by: disposBag)
        
        subject.onNext("두번째 onNext")
        
        //print
        첫번째 observer === next(초기값)
        첫번째 observer === next(첫번째 onNext)
        두번째 observer === next(첫번째 onNext)
        첫번째 observer === next(두번째 onNext)
        두번째 observer === next(두번째 onNext)

이후 error , completed 의 방출에서의 observer 반응은 publishSubject 와 동일하게 동작합니다.

        let subject = BehaviorSubject<String>(value: "초기값")
        // 구독전 Event 없음
        let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
        firstObserver.disposed(by: disposBag)
        subject.onNext("첫번째 onNext")
        // seoncdObserver 는 최신값이 초기값이 아니기때문에 초기값을 받을수 없다.
        let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
        secondObserver.disposed(by: disposBag)
        
        subject.onNext("두번째 onNext")
    
        subject.onCompleted()
        // completed 이후로는 어떠한 subject 의 emit 은 종료되니 observer 는 어떠한 event도 받지못한다.
        let thirdObserver = subject.subscribe { print("세번째 observer === \($0)")}
        thirdObserver.disposed(by: disposBag)
        subject.onNext("세번째 onNext")

적재적소:
User 가 Profile 을 수정할 경우 BehaviorSubject와 binding 하여 data가 변하면 최신 data로 변경하기 용이 하다.

ReplaySubject

ReplaySubject 는 방출되었던 최신 값들을 지정한 bufferSize에 맞춰 묶은뒤 방출합니다.

또한 생성자가 아닌 create 메소드를 사용하여 생성합니다.

※ 버퍼 사이즈는 메모리에 할당되므로 버퍼 사이즈가 커질 수록 메모리에 큰 부하를 갖는 것을 주의 해야 합니다.

  • 첫번째 : PublishSubject
  • 두,세 번째 : 구독자(subscribers)
  • 위로 올라가는 화살표 : 구독
  • 아래로 내려오는 화살표 : 방출

하지만 구독이 먼저 이루어지고 방출이 시작된다면? bufferSize 만큼 저장하지 않고 바로바로 방출합니다.

        let subject = ReplaySubject<Int>.create(bufferSize: 2)
        (1...10).forEach { subject.onNext($0) }
        
        subject.subscribe { print($0)}.disposed(by: disposBag)
        
        
        let first = subject.subscribe { print("첫번째 observer == \($0)")}
        first.disposed(by: disposBag)
        
        
        let second = subject.subscribe { print("두번째 observer == \($0)")}
        second.disposed(by: disposBag)
        
        // 구독 이후 onNext 되는 Event 는 buffer 로 묶지 않고 전체 방출한다.
        subject.onNext(99)
        subject.onNext(999)
        subject.onNext(9999)
    //print    
next(9) // buffer
next(10) // buffer
첫번째 observer == next(9) // buffer
첫번째 observer == next(10) // buffer
두번째 observer == next(9) // buffer
두번째 observer == next(10) // buffer
// 묶지 않고 방출
next(99)
첫번째 observer == next(99)
두번째 observer == next(99)
next(999)
첫번째 observer == next(999)
두번째 observer == next(999)
next(9999)
첫번째 observer == next(9999)
두번째 observer == next(9999)

이 밖에도 기존의 subject 동작은 기본적으로 같습니다.
completed, error 가 방출되면 동일하게 반응 합니다.

적재적소:
방대한 데이터가 아닌 UI 에서 부담없이 볼수 있는 최근검색 부분에 사용하면 좋을것 같다.
최근 검색의 경우 모바일에선 너무 많은 Data를 나타내고 있지 않기에 메모리의 무리도 없다.

AsyncSubject


AsyncSubject 는 기존 Subject 들과 달리 이벤트를 전달하는 시점에 차이가 있습니다.

다른 Subject 들은 이벤트를 방출 할시 구독자에게 이벤트를 전달합니다.

하지만 AsyncSubject 는 Completed 이벤트가 전달되기 전까지는 어떠한 이벤트도 구독자에게 전달하지 않습니다.

또한 Completed 이벤트가 전달되면 가장 최신 이벤트만을 next 로 Observer 에게 전달합니다.

        let subject = AsyncSubject<String>()
        
        let firstObserver = subject.subscribe { print($0) }
        firstObserver.disposed(by: disposBag)
        
        subject.onNext("첫번째 넥스트")
        subject.onNext("두번째 넥스트")
        subject.onNext("세번째 넥스트")
        
        //completed 되지 않았기 때문에 출력문 없음

컴플리트가 되지 않아 Event를 Observer 가 전달받지 못하였습니다. 그렇기에 Observer는 Event를 출력할수 없습니다.

        let subject = AsyncSubject<String>()
        
        let firstObserver = subject.subscribe { print($0) }
        firstObserver.disposed(by: disposBag)
        
        subject.onNext("첫번째 넥스트")
        subject.onNext("두번째 넥스트")
        subject.onNext("세번째 넥스트")
        subject.onCompleted()
        //print
        //next(세번째 넥스트)
        //completed

하지만 completed Event 가 Emit 되면 next 로 가장 최신의 값 completed 를 Observer 에게 전달합니다.

또한 Error Event 가 방출 되면 최근 next 이벤트 없이 Error Event 만 방출하고 종료됩니다.

Hot , Cold Observable

Hot Observable 과 Cold Observable 의 차이점 입니다.

Hot -multicasting-

  • 생성과 동시에 Event 를 방출한다.
  • 구독 이후 방출한 Event만 Observer 는 관찰할수 있다.

Cold - unicasting-

  • Observer 가 구독 하기 전까지 Event 를 방출하지 않는다.
  • Observable 이 방출하는 Event 전체를 관찰 할수 있다.
        print("Start")
        let observable = Observable<String>.create { observer in
            observer.onNext("방출")
            return Disposables.create()
        }
//            .subscribe(onNext: {
//            print($0)
//        })
        print(observable)
        
        
        let sub = PublishSubject<String>()
        sub.onNext("방출")
        print(sub)
//print
Start
RxSwift.(unknown context at $111253298).AnonymousObservable<Swift.String>
RxSwift.PublishSubject<Swift.String>

observable 은 생성되지 않았습니다.
하지만 PublishSubject 는 생성되었음을 알수 있습니다.

이유에 대해서 알아보자면

Observable.create 를 이용해 Observable 을 생성하게 되면 return 으로 Observable을 Disposables.create 만들어 반환합니다.

Observable은 Disposable 을 상속 받고있고 있습니다.

여기서 Observable 에게 subscribe 가 이루어지면
Observable 은 생성되어 방출하기 시작합니다.

Start
방출
RxSwift.(unknown context at $10245ac94).BinaryDisposable //BinaryDisposable 로 변함.
RxSwift.PublishSubject<Swift.String>

또한 Hot 은 Mulitcasting cold 는 unicasting 이기 때문에 Hot은 데이터를 구독자 간에 공유가 되지만 cold는 구독이 완료된 후에 생성,방출 하기 때문에 구독자마다 항상 새로운 실행이 이루어져 값을 공유할수 없습니다.

cold 는 값이 안에서 생성
Hot 은 값이 밖에서 생성

        let coldObservable = Observable<Int>.create { observer in
            observer.onNext(Int.random(in: 0 ..< 100))
            return Disposables.create()
        }
        //cold =  구독 X data 생성 in 방출 X
        
        // --------------
        
        let randomNum = Int.random(in: 0 ..< 100 )
        let hotObservable = Observable<Int>.create { observer in
            observer.onNext(randomNum)
            return Disposables.create()
        }
        //hot = 데이터를 외부에서 생성한뒤 대입해준다.
        
        coldObservable.subscribe { print("Cold 첫번째 구독 === \($0)") }.disposed(by: disposBag)
        coldObservable.subscribe { print("Cold 두번째 구독 === \($0)") }.disposed(by: disposBag)
        
        hotObservable.subscribe { print("Hot 첫번째 구독 === \($0)")}.disposed(by: disposBag)
        hotObservable.subscribe { print("Hot 두번째 구독 === \($0)")}.disposed(by: disposBag)
//print

Cold 첫번째 구독 === next(26)
Cold 두번째 구독 === next(23)
Hot 첫번째 구독 === next(51)
Hot 두번째 구독 === next(51)
profile
iOS/Android/FE/BE

0개의 댓글