RxSwift

Subject

motosw3600 2022. 2. 8. 19:45

Subject

  • Observable(이벤트 발행)과 Observer(구독)모두 동작가능한 타입
  • 동적으로 값을 발행 가능
  • 발행하는 동시에 수동으로 값을 관찰가능
  • Observable 및 관찰 가능한 ObserverType을 채택하고 있다.
  • MVVM구조에서 User Interaction에 의한 이벤트를 viewModel의 Subject가 구독하고, 구독을 통해 발행된 값을 View가 구독함으로 써 MVVM동작 연결 가능

Observable과 Subject의 차이

  • Subject는 Observable과 Observer역할을 모두가능
  • Observable은 Unicast방식으로 observer하나만을 subscribe
  • Subject은 Multicast방식(여러 Observer에게 이벤트 발행 가능)
  • 네트워크 요청
    • Observable은 단 하나의 함수이기 때문에 어떤 상태도 가지지 않으므로 새로운 Observer에 대해 관찰 가능한 create코드를 반복 실행하게 된다. 코드는 각 Observer에대해 호출되므로 HTTP호출인 경우 각 Observer에 대해 호출(비효율적)
    • Subject는 관찰자 세부 정보를 저장하고 코드를 한번만 실행하고 모든 Observer에게 값을 발행

 

Subject종류

PublishSubject

  • PublishSubject는 Subscriber가 새로운 이벤트만 전달받을때 사용
  • Subscribe는 이전에 발행한 이벤트에 대해선 모른다, 이후 발행된 이벤트만 구독
  • 정각 알림 같은 경우 정각 이후에 사용자들은 정각 이전의 알림은 알 필요가 없다.

1) 첫번째 Sequence에서 (1)이벤트 발행. subscriber가 없으므로 이벤트 받지 못함

2) 두번째 Sequence에서 첫번째 Sequence 구독. 첫번째 Sequnce에서 (2)이벤트 발행. 두번째 Sequence (2) 전달 받음

3) 세번째 Sequence에서 첫번째 Sequence 구독. 첫번째 Sequnce에서 (3)이벤트 발행. 두번와 세번째 Sequence (3) 전달 받음

 

let subject = PublishSubject<String>()
subject.onNext("Publish") // subscribe없음 발행x

let subscriptionOne = subject.subscribe(onNext: {
    print("1)", $0)
})

subject.on(.next("1")) // 1) 1
subject.onNext("2") // 1) 2

let subscriptionTwo = subject.subscribe({ event in
    print("2)", event.element ?? event)
})

subject.onNext("3") // 1) 3, 2) 3
subscriptionOne.dispose() // subscriptionOne 종료

subject.onNext("4") // 2) 4

subject.onCompleted() // complete
subject.onNext("5") // complete이후 발행x

subscriptionTwo.dispose() // subscriptionTwo 종료

let disposeBag = DisposeBag()

subject.subscribe {
    print("3)", $0.element ?? $0) // 3) completed만 출력
}
.disposed(by: disposeBag)

subject.onNext("6") // 발행 x

 

위 코드에서 onCompleted()하고 끝난 시퀀스를 구독할 경우 complete이벤트가 emit된다.

모든 Subject는 complete또는 error이벤트로 인해 종료된 이후 발생한 subscribe에 해당 event를 다시 emit한다.

BehaviorSubject

  • PublishSubject가 이전에 발생한 이벤트를 전달받을 수 없다면 BehavierSubject는 최신 이벤트(.next)를 전달 받음
  • 초기값을 설정해 주어야 한다.
  • 최근 발생한 이벤트를 알아야 될 경우 사용. ex) 가장 최근에 수정된 프로필을 보여줘야 할 경우

1) 두번째 Sequence가 첫번째 Sequnce를 구독하는 즉시 (1)이벤트 발행

2) 세번째 Seqeunce가 첫번째 Sequence를 구독하는 즉시 첫번째 Sequence의 최신 이벤트(2) 발행

enum BehaviorSubjectError: Error {
    case SubjectError
}

let subject = BehaviorSubject(value: "Initial Value")
let disposeBag = DisposeBag()

subject.onNext("X")

subject.subscribe {
    print("1)", $0)
}
.disposed(by: disposeBag) // 1) next(X)

subject.onError(BehaviorSubjectError.SubjectError)
// error(SubjectError)

subject.subscribe {
    print("2)", $0)
}
.disposed(by: disposeBag)
// error(SubjectError)

 

subject를 구독하자마자 최신값("X")를 전달 받는다.

ReplaySubject

  • 최근에 발행된 이벤트들을 여러게 캐싱하고 있다가 새로운 Subscriber가 발생하면 한번에 전달.
  • 캐시의 개수는 설정 가능
  • 최근 검색어를 보여주고 싶을 경우(최대 몇개 지정)

1) 첫번째 Sequence는 버퍼 사이즈가 2인 ReplaySubject

2) (1), (2)이벤트가 emit된 이후 세번째 Sequence가 첫번째 Sequence를 구독하는 즉시 캐싱된 (1), (2)이벤트 전달

 

enum SubjectError: Error {
    case subjectError
}

let subject = ReplaySubject<String>.create(bufferSize: 2)
let disposeBag = DisposeBag()

subject.onNext("1")
subject.onNext("2")
subject.onNext("3")

subject.subscribe {
    print("1)", $0)
}
.disposed(by: disposeBag)
// 1) next(2)
// 1) next(3)

subject.subscribe {
    print("2)", $0)
}
.disposed(by: disposeBag)
// 2) next(2)
// 2) next(3)

subject.onNext("4") // 1) next(4), 2) next(4)
subject.onError(SubjectError.subjectError) // 1) error(subjectError), 2) error(subjectError)

subject.subscribe {
    print("3)", $0)
}
.disposed(by: disposeBag)
// 3) next(3)
// 3) next(4)
// 3) error(subjectError)

 

마지막 구독한 subscribe는 최근에 발행된 이벤트 3, 4(버퍼size: 2)를 전달받음

Note: 버퍼사이즈가 클 수록 메모리에 과부하가 갈 수 있으므로 적당한 캐싱 정책을 사용

 


Relay

  • RxSwift인 Subject와 달리 Relay는 Rxcocoa의 클래스
  • Subject는 .completed, .error의 이벤트가 발생하면 구독이 종료되는 반면, Relay는 .complted, .error를 발생시키지 않고 dispose가 될 때 까지 계속 작동(UI Event 사용하기 적절, 스트림이 끊어지지 않고 계속 진행되어야 할때 사용)
  • Relay는 get-only형태로 .value를 통해서 값확인 가능
  • .accept()로 이벤트 발행

PublishRelay

  • PublishRelay는 PublishSubject의 Wrapper클래스
  • PublishSubject처럼 구독 이후 발생한 이벤트만 수신

BehaviorRelay

  • BehaviorSubject의 Wrapper클래스
  • .value로 현재값 확인 가능
let disposeBag = DisposeBag()

let relay = BehaviorRelay(value: "Initial Value")

relay.accept("New Value")

relay
    .subscribe {
        print("1) ", $0) // 1) next(New Value)
    }
    .disposed(by: disposeBag)

relay.accept("1") // 1) next(1)

relay
    .subscribe {
        print("2) ", $0) // 2) next(1)
    }
    .disposed(by: disposeBag)

relay.accept("2")
// 1) next(2)
// 2) next(2)

 

Driver

  • Observable에 특화된 형태로 구독만 가능
  • UI관련 작업시 하나의 Relay에 여러 Driver를 생성해서 하나의 이벤트를 여러곳에서 구독
  • 에러를 발생하지 않으므로 accpet로 값 발행
let dispostBag = DisposeBag()

let behavior = BehaviorRelay(value: 0)
let driver1 = behavior.asDriver()
let driver2 = behavior.asDriver()

driver1
    .drive(onNext: {
        print("driver1) ", $0)
    })
    .disposed(by: dispostBag)

driver2
    .drive(onNext: {
        print("driver2) ", $0)
    })
    .disposed(by: dispostBag)

behavior.accept(3)

// driver1) 0
// driver2) 0
// driver1) 3
// driver2) 3

 

 

참고 : raywenderlich RxSwift Programming with Swift

'RxSwift' 카테고리의 다른 글

RxFlow  (0) 2022.03.30
RxViewController  (0) 2022.03.03
Operator  (0) 2022.02.09
Observable  (0) 2022.02.07
RxSwift란?  (0) 2022.02.07