옵저버블은 어떤 데이터를 내보내야할지가 이미 정해진 스트림. 외부에서 데이터를 주입시켜줄 수 없기 때문에 구독만 가능하다.
Subject
서브젝트는 외부에서 동적으로 데이터를 주입시켜줄 수 있는 스트림. 옵저버 이면서 옵저버블(구독)도 가능한 양방향성을 가진 애다.
2. RxCocoa
swift 요소들을 uikit에 적용시킨 녀석이다. myLabel.rx 라고 치면 제공되는 애들인데 리턴값은 Reactive { get set } 이다. 지금 사용한 값이 UILabel이므로 text라는 속성을 가지고 있는데, 얘는 타입이 Binder<String?>이다. myLabel.rx.text //Binder<String?> 그리고 바인더는 bind할 수 있다.
옵저버블 중에 UI용이 있는데, UI전용 옵저버블은 RxCocoa에서 제공하는 Driver이고, UI전용 서브젝트는 RxRelay에서 제공하는 Relay이다.
UI 작업의 특징
항상 메인쓰레드에서 돌아가야 한다.
에러가 나더라도 절대로 스트림이 끊어지면 안된다. (앱이 동작을 안하는거니까)
Relay 특징
원본 소스 코드를 보면 서브젝트를 랩핑해서 만들어진 것을 볼 수 있다. 얘는 completed, .error이 없다. 오로지 next만 있다.
그리고 뜻그대로 받아들이는것밖엔 할 수 없으니 accept라고 부른다.
연산프로퍼티로 구현되어있고 내부에 onNext만 있는 것을 볼 수 있다.
importRxSwift/// PublishRelay is a wrapper for `PublishSubject`.////// Unlike `PublishSubject` it can't terminate with error or completed.publicfinalclassPublishRelay<Element>:ObservableType {privatelet subject: PublishSubject<Element>// Accepts `event` and emits it to subscriberspublicfuncaccept(_event: Element) { self.subject.onNext(event) }/// Initializes with internal empty subject.publicinit() { self.subject =PublishSubject() }/// Subscribes observerpublicfuncsubscribe<Observer:ObserverType>(_observer: Observer) -> Disposable where Observer.Element==Element { self.subject.subscribe(observer) }/// - returns: Canonical interface for push style sequencepublicfuncasObservable() -> Observable<Element> { self.subject.asObservable() }}
헷갈릴 수 있는 또하나의 특징
쉽게 헷갈릴 수 있는 또하나의 특징이 있다.
위에서 본 것처럼 relay는 서브젝트를 랩핑한 것 뿐이다.
그러면 accept는 알겠다. 옵저버 이자 옵저버블이라는 것도 알겠다. 그럼 구독할 때는 어떻게 될까 ?
구독할때는 일반적인 서브젝트와 마찬가지로 subscribe, bind, drive를 모두 사용할 수 있다.
다시말하면, relay라고 해서 main thread에서 돌아간다는 보장이 없다는 뜻이다. 아래코드를 보자.
let relay = PublishRelay<Int>()//let relay = BehaviorSubject.init(value: 1)var disposeBag =DisposeBag()Observable.from([1,2,3,4,5]) .observe(on: ConcurrentDispatchQueueScheduler(qos: .background)) .bind(to: relay) .disposed(by: disposeBag)relay// .observe(on: MainScheduler.instance) .do(onNext: { print(Thread.main, $0, "thread...") }) .subscribe()//1.//let relay = PublishRelay<Int>() 출력 //.observe(on: MainScheduler.instance) 활성화 <_NSMainThread:0x6000003d4500>{number =1, name = main} 1 thread...<_NSMainThread:0x6000003d4500>{number =1, name = main} 2 thread...<_NSMainThread:0x6000003d4500>{number =1, name = main} 3 thread...//2.//let relay = PublishRelay<Int>() 출력 //.observe(on: MainScheduler.instance) 비활성화 <_NSMainThread:0x600000bb04c0>{number =1, name = (null)} 1 thread...<_NSMainThread:0x600000bb04c0>{number =1, name = (null)} 2 thread...<_NSMainThread:0x600000bb04c0>{number =1, name = (null)} 3 thread...//3. //let relay = BehaviorRelay.init(value: 1) 출력//.observe(on: MainScheduler.instance) 비활성화 <_NSMainThread:0x6000010084c0>{number =1, name = main} -1 thread...<_NSMainThread:0x6000010084c0>{number =1, name = (null)} 1 thread...<_NSMainThread:0x6000010084c0>{number =1, name = (null)} 2 thread...<_NSMainThread:0x6000010084c0>{number =1, name = (null)} 3 thread...
위에 출력문을 보자. 각 1,2,3번의 조건대로 동작시킨 것이다.
1번을 보자. 퍼블리시릴레이, 출력전에 메인스케쥴러로 바꿔주었다. 그러면 셋다 main쓰레드에서 출력된다.
2번. 1번과 같이 퍼블리시 릴레이, 대신에 이건 출력전에 메인스케쥴러로 바꿔주지 않았다. 그랬더니? 메인쓰레드에서 돌지않는다. 왜? 옵저버블을 생성할때 백그라운드에서 돌게 설정을 했기 때문이다.
3번. 3번도 재미있다. 3번은 2번과 조건이 같으나 퍼블리시가 아니라 비헤이비어 릴레이다. 근데 재밌는게 초기값이 들어가는 -1만 메인쓰레드에서 돈다는 것이다. 이건 음..엑코가 정하는건지 뭔지 이유는 모르지만, 릴레이나 내부코드에서 따로 어디쓰레드에서 돌아라라고 지정되어있진 않지 싶다. 근데 알고는 있어야겠지.
결론 -> relay라고 해서 main thread에서 돌아간다는 보장이 없다. 그래서 구독할때 UI작업이라면 driver를 사용하거나, 또는 obseron + catchjustreturn 오퍼레이터를 사용해서 작업을 해줘야 된다는 것이다.
4. Traits
한글로 번역하면 특성?이라는 뜻이지만 직역하면 뭔가 좀 어색하다. 그렇지않나 ?.. 특성이라고 말하긴 좀 그렇고.. 기능?? 메소드??같은느낌이 오히려 더 잘어울리는듯.. 대충 그렇게 이해하자 공식문서에 온갖 예시와 설명이 기깔나게 나와있다. 항시 공식문서를 보자. 공식문서 https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md
컨트롤 이벤트는 뭐냐면, myButton.rx.tap 같은 애들을 말한다. 아까 말했지않나? .rx 는 Reactive 타입이라고. 그리고 버튼클릭을 뜻하는 tap의 구현을 보면 ControlEvent로 구현되어 있다. 아래의 코드를 보자.
extensionReactivewhereBase:UIButton {/// Reactive wrapper for `TouchUpInside` control event.publicvar tap: ControlEvent<Void> {controlEvent(.touchUpInside) }}publicstructControlEvent<PropertyType>:ControlEventType {publictypealiasElement= PropertyTypelet events: Observable<PropertyType>/// Initializes control event with a observable sequence that represents events.////// - parameter events: Observable sequence that represents events./// - returns: Control event created with a observable sequence of events.publicinit<Ev:ObservableType>(events: Ev) where Ev.Element==Element { self.events = events.subscribe(on: ConcurrentMainScheduler.instance) }
아까 말한것처럼 .rx 같은 경우는 extension Reactive 에 base로 UIButton인 것을 볼 수 있다. 그리고 내부에 tap이라는 변수로 ControlEvent 타입이 있다.
또, ControlEvent의 내부구현을 보면
얘도 events라는 옵저버블이다. 또 중요한게 init을 보자. 그러면 얘같은 경우는 events.subscribe(on: ConcurrentMainScheduler.instance) 로 구현되어 있다. 여기서 알수있는것 은 두개다.
ControlProperty 공통점
ControlEvent 특징
subscribe를 해줄 필요가 없다. 이미 되어있으니
메인 쓰레드에서 돌아가는게 보장된다는 것이다.
ControlProperty / ControlEvent 차이점
UISearchBar, UISegmentedControl 같은 애들은 ControlProperty