Youngminah/TIL

RxCocoa

Closed this issue · 0 comments

bind(to:)

image

  • observable 바인딩
  • Producer는 값을 만들어낸다.
  • Receiver는 만들어진 값을 수신하고 처리한다.
  • observable을 다른 속성에 바인딩하기 위해서는 Receiver가 ObservableType 이어야한다.
  • Receiver는 값을 반환할 수 없다. 이 것이 RxSwift에서 사용하는 일반적인 바인딩 규칙이다.
  • 요약하자면, bind(to:) 함수는 subscribe()의 특별 맞춤 버전이다.
  • 바인더는 Error이벤트를 받지 않는다. (종료되지 않기위해)
  • 바인더는 UI Binding에 사용된다.
  • 바인더는 메인스레드에서 실행되는것을 보장되어야한다.

바인더 예제

image

  • 위가 텍스트 필드 아래가 레이블
  • 텍스트필드에 한글자 쓸 때마다 레이블이 업데이트 된다.
textField.rx.text
    .observe(on: MainScheduler.instance) //메인스레드에서 실행 보장 위함
    .subscribe(onNext: {
        label.text = $0
    })
    .disposed(by: disposeBag)
  • 위의 예제를 바인더를 이용하여 아래의 예제로 재구성
textField.rx.text
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)

RxCocoa Traits

  • RxCocoa는 bindTo 외에도 Cocoa 프레임워크와 UIkit을 다룰 많은 진보된 기능들을 제공
  • Trait은 UI와 함께 사용되도록 독점적으로 생성된 observable 항목의 특수한 구현을 제공
  • Trait는 직관적이고 작성하기 쉬운 코드를 작성하는데 도움이 되는 Observable의 특수 클래스다. (특히 UI 작업할 때)
  • 참고: RxSwift의 traits와 마찬가지로 RxCocoa의 traits도 작업에 도움이 되는 특별기능일 뿐 사용은 선택적이다.
  • Trait를 억지로 사용할 필요는 없다.
  • 처음에는 순수히 Subject나 Observable만 쓰는 것도 나쁘지 않다.
  • 하지만 만약 컴파일링 중에 또는 UI와 관련된 어떤 예정된 법칙을 체크하고 싶을 때, ❗️
  • Trait은 아주 강력한 기능을 제공하며 시간 절약에도 좋다.
  • Trait을 사용하면 .observeOn(MainScheduler.instance) 호출에 대해 잊어버려도 좋다.
  • 또한 background 쓰레드에서 UI를 생성할 필요도 없다.
  • Driver와 ControlProperty가 지금은 어려워 보일 수 있다.
  • 천천히 하나씩 확인해보자.

Driver와 Signal

  • 에러를 방출하지 않는 특별한 observable
  • 모든 과정은 main thread에서 이루어진다. (여기까지는 바인드와 비슷함)
  • 부수작용들 공유 가능 (바인드와 다른점) ❗️
  • 둘의 차이점은 Driver는 구독하기 전에 초기값 또는 최신값 replay할 수 있다.
  • Signal은 구독한 이후에 발생하는 값만 전달
  • Signal은 emit함수로 이벤트 처리 / Driver는 drive함수로 이벤트 처리

Driver ❗️❗️

  • 부수작용 공유 !!
  • 구독하기 전에 초기값 또는 최신값 replay할 수 있다.
드라이버 사용전
let result = inputField.rx.text.asDriver()
  .flatMapLatest { validateText($0) }

result
   .map { $0 ? "Ok" : "Error" }
   .bind(to: resultLabel.rx.text)
   .disposed(by: bag)

result
   .map { $0 ? UIColor.blue : UIColor.red }
   .bind(to: resultLabel.rx.backgroundColor)
   .disposed(by: bag)

result
   .bind(to: sendButton.rx.isEnabled)
   .disposed(by: bag)
share를 이용한 뒤 수정 후
let result = inputField.rx.text.asDriver()
  .flatMapLatest { 
    validateText($0) 
      .observeOn(MainScheduler.instance)
      .catchErrorJustReturn(false) // 구독자로 에러전달이 아닌 false이벤트 전달.
  }
  .share()

result
   .map { $0 ? "Ok" : "Error" }
   .bind(to: resultLabel.rx.text)
   .disposed(by: bag)

result
   .map { $0 ? UIColor.blue : UIColor.red }
   .bind(to: resultLabel.rx.backgroundColor)
   .disposed(by: bag)

result
   .bind(to: sendButton.rx.isEnabled)
   .disposed(by: bag)
  • 드라이버를 사용하면 더 간단하게 바꿀 수 있다.
드라이버 사용후
let result = inputField.rx.text.asDriver()
  .flatMapLatest {
      validateText($0)
          .asDriver(onErrorJustReturn: false)
  }

result
   .map { $0 ? "Ok" : "Error" }
   .drive(resultLabel.rx.text)
   .disposed(by: bag)

result
   .map { $0 ? UIColor.blue : UIColor.red }
   .drive(resultLabel.rx.backgroundColor)
   .disposed(by: bag)

result
   .drive(sendButton.rx.isEnabled)
   .disposed(by: bag)
  • 드라이버를 쓰지 않는다면 문제점은 각각의 구독자마다 sequence가 생성.
  • 즉 공유하지 않는다.
  • 만약 네트워크 통신이라면 심각한 오버헤드가 발생할 것이다.
  • 그리고 에러이벤트가 발생했을때, 문제점이있다.
  • share를 이용해도 되지만 그럴경우 Main스레드에서 실행하는 코드, 에러핸들링 코드 등등 직접 코드로 구현해주어야한다.

Signal

  • 구독한 이후에 발생하는 값만 전달

Rx Extension

  • extension Reactive where Base: T {}
  • 이러한 형태로 쓰인다.
extension Reactive where Base: UIView {
    var sizeToFit: Binder<Void> {
        return Binder(base) { base, _ in
            base.sizeToFit()
        }
    }
}
Driver.just(Void())
    .drive(button.rx.sizeToFit)
    .disposed(by: disposeBag)