본문 바로가기
iOS/RxSwift

RxSwift: Subjects

by HaningYa 2020. 7. 10.
728x90


Subjects

observable 을 만들고 subscribe를 하고 작업이 끝난 뒤 dispose 하는 것 까지 배워봤다.

앱을 개발할 때 runtime동안 하나씩 새로운 값을 obervable에 추가하고 subscriber에 에 emit 하는 동작은 자주 쓰인다.

그래서 이걸 쉽게 하기 위해 Observable 과 Observer 둘의 역할을 동시에 할 수 있는 Subject가 생겼다.


Create Subjects

Subject를 만들고 Subscribe를 한 뒤에 추가되는 값들을 받아올 수 있다.

 func createSubject(){
        let subject = PublishSubject<String>()
        subject.onNext("Is anyone listening?")
        
        let subscriptionOne = subject
            .subscribe(onNext: { string in
                print(string)
            })
        //위 코드까지만 쓰면 print되지 않는다.
        //왜냐하면 subscribe 된 후에 추가된 값부터 볼 수 있기 때문
        subject.on(.next("1")) //1
        //subscribe 후에 추가된 값 1은 print된다.
        subject.onNext("2") //1 2
    }

Four Subject types and two Relay types

Subject

  1. PublishSubject: empty 로 시작해 새로운 element만 subscribers에게 emit 한다.
  2. BehaviorSubject: 초기값으로 시작해 최신 element만 subscribers에게 replay한다.
  3. ReplaySubject: buffer size로 초기화 되어 사이즈 만큼 element 를 관리해 subscribers 에게 replay한다.
  4. AsyncSubject: subject가 .completed를 받으면 오직 마지막 .next event 만 emit 한다. 

Relay

Relay는 연관된 subject들을 모은다. 오직 .next 이벤트만 허용하며 .complete 나 .error는 사용할 수 없다. 
즉 non-terminating sequence에 적합하다.

  1. PublishRelay
  2. BehaviorRelay

*Relay 는 RxCocoa에 있다. (RxSwift에서 deprecated 되었다.)


Working with Publish subjects

func createSubject(){
        let subject = PublishSubject<String>()
        subject.onNext("Is anyone listening?")
        
        let subscriptionOne = subject
            .subscribe(onNext: { string in
                print(string)
            })
        //위 코드까지만 쓰면 print되지 않는다.
        //왜냐하면 subscribe 된 후에 추가된 값부터 볼 수 있기 때문
        subject.on(.next("1")) //1
        //subscribe 후에 추가된 값 1은 print된다.
        subject.onNext("2") //1 2
        subscriptionOne.dispose()
        
        let subscriptionTwo = subject
            .subscribe { event in
                print("2)", event.element ?? event)
        }
        subject.onNext("3")
        subject.onNext("4")
        
        subject.onCompleted()
        subject.onNext("5") //this will not be emitted since subject is terminated
        subscriptionTwo.dispose() //dispose done subscriptions
        let disposeBag = DisposeBag()
        
        subject.subscribe {
            print("3)",$0.element ?? $0)
        }.disposed(by: disposeBag)
        subject.onNext("?")
        
    }
    
//        1
//        2
//        2) 3
//        2) 4
//        2) completed
//        3) completed

Working with Behavior Subject

publish subject와 비슷하나 가장 최근의 .next 이벤트를 새로운 subscriber 에게 replay 해준다. (subscribe하면 그 전 마지막 값을 줌)
(publish subject는 subscribe 한 뒤에 오는 값을 받을 수 있음. 이전에 있던 값 모름)

pre-populate, 즉 먼저 view 를 이전의 최근 데이터로 생성하고 싶을 때 사용한다.

예를들어 앱에서 새로운 데이터를 fetch 하기 전 이전에 있던 데이터로 먼저 뷰를 보여주는 상황이다.

func createBehaviorSubject(){
        let subject = BehaviorSubject(value: "Inital value")
        let disposeBag = DisposeBag()
        subject.onNext("before subscribe")
        subject
            .subscribe { event in
                print("1)", event.element ?? event)
        }
        .disposed(by: disposeBag)
        subject.onNext("after subscribe")
    }
//1) before subscribe
//1) after subscribe

 


 

근데 만약 가장 최근 데이터 한개가 아닌 이전 데이터 들이 필요 하다면?? 예를들어 검색기록 같은걸 위해서 replay subject가 있다.

Replay Subject

replay subect는 임시적으로  원하는 만큼의 최근 emit된 element 를 캐시나 버퍼로 가지고 있다.

새로운 subscriber가 생길 때 해당 버퍼의 데이터를 모두 제공한다.

주의할 점은 이미지와 같은 큰 사이즈를 버퍼에 넣으면 메모리 다 잡아먹는다. 또한 동일한 이유로 replay subject에 array를 넣는건 비추

func replaySubject(){
        let subject = ReplaySubject<String>.create(bufferSize: 2)
        let disposeBag = DisposeBag()
        
        subject.onNext("1")
        subject.onNext("2")
        subject.onNext("3")
        
        subject
            .subscribe { event in
                print("1) ", event.element ?? event)
        }.disposed(by: disposeBag)
        
        subject.onNext("4")
        
        subject.subscribe { event in
            print("2) " , event.element ?? event)
        }.disposed(by: disposeBag)
    }
    
//1)  2
//1)  3
//1)  4
//2)  3
//2)  4

특히 중간에 error가 발생하면 stop event 가 emit 되기 전에 subscriber 에게 error와 함께 buffer 가 한번 더 전달된다.

subject.dispose() 를 명시적으로 선언해주면 subscriber는 subject 가 disposed 되었다는 error 만 receive 한다.


 

Relays

단순히 observable type의 현재값을 알고 싶을 땐 relays를 사용한다.

Relay는 replay behavior 를 wrap 한 것이다. 

특이한 점은 onNext(_:) 를 사용하지 않고 accept(_:)를 사용한다.

  • PublishRelay -> wrap of PublishSubject
  • BehaviorRelay -> wrap of BehaviorSubject

PublishRelay

 func relayPractice(){
        let relay = PublishRelay<String>()
        let disposeBag = DisposeBag()
        relay.accept("is anyone home?")
        
        relay.subscribe(onNext: {
            print($0)
        })
            .disposed(by: disposeBag)
        relay.accept("1")
    }
  //1

 

BehaviorRelay

 func behaviorRelay(){
        let relay = BehaviorRelay(value: "Initial Value")
        let disposeBag = DisposeBag()
        
        relay.accept("New init value")
        relay.subscribe { event in
            print("1) ",event.element ?? event)
        }
        .disposed(by: disposeBag)
        
        relay.accept("1")
        relay
            .subscribe { event in
                print("2) ",event.element ?? event)
        }.disposed(by: disposeBag)
        relay.accept("2")
        print(relay.value)
    }
    
//1)  New init value
//1)  1
//2)  1
//1)  2
//2)  2
//2

 


 

 

728x90

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

RxSwift background knowledge  (0) 2021.03.25
RxSwift: Observable & Subject in practice  (0) 2020.07.13
RxSwift: Observable  (0) 2020.07.09
RxSwift: Hello RxSwift!🖐  (0) 2020.07.08
RxSwift를 사용한 비동기 프로그래밍 #4 [마지막]  (0) 2020.06.03

댓글