관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - Swift] (RxSwift) ControlEvent, ControlProperty, Binder 이해하기 (#Reactive Extension) 본문

iOS 응용 (swift)

[iOS - Swift] (RxSwift) ControlEvent, ControlProperty, Binder 이해하기 (#Reactive Extension)

jake-kim 2022. 11. 6. 22:47

Reactive Extension

  • Reactive란?
    • base를 초기화로 넘겨주면, 내부에서 base property를 들고있는 상태
@dynamicMemberLookup
public struct Reactive<Base> {
    public let base: Base
    
    public init(_ base: Base) {
        self.base = base
    }

    public subscript<Property>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Property>) -> Binder<Property> where Base: AnyObject {
        Binder(self.base) { base, value in
            base[keyPath: keyPath] = value
        }
    }
}
  • Reactive Extension
    • extension으로 Base 타입을 정해주고, 구현해주면 사용하는쪽에서 button.rx.tap으로 접근이 가능
    • base.rx.tap
// UIButton+Rx

extension Reactive where Base: UIButton {
    public var tap: ControlEvent<Void> {
        controlEvent(.touchUpInside)
    }
}
  • 위에서 나오는 controlEvent는?
    • UIControl.event를 wrapping한 형태 (ControlEvent는 Observable타입 중 하나)
extension Reactive where Base: UIControl {
    public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> {
        let source: Observable<Void> = Observable.create { [weak control = self.base] observer in
                MainScheduler.ensureRunningOnMainThread()

                guard let control = control else {
                    observer.on(.completed)
                    return Disposables.create()
                }

                let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in
                    observer.on(.next(()))
                }

                return Disposables.create(with: controlTarget.dispose)
            }
            .take(until: deallocated)

        return ControlEvent(events: source)
    }
}

ControlEvent, ControlProperty, Binder 차이

  • Observer 타입은 값을 관찰 못하고 방출만 할 수 있고, Observable 타입은 값을 관찰만 할 수 있는데 이와 유사한 기능을 나누어 놓은 것
  • Observer와 Observable로 표현할 수 있지만 굳이 새로 만들어서 사용하는 이유?
    •  RxCocoa의 Traits에서 제공하는 프로퍼티
    • Traits이 UI만을 처리하기 위한 프로퍼티이므로 MainScheduler에서 실행
    • UI을 처리하기 위한 개념이므로 error와 fail 이벤트가 존재하지 않음 (단 메모리 해제시 complete 이벤트는 방출)
  • 차이
    • ControlEvent: `이벤트` (값 관찰만 가능)
    • ControlProperty: `프로퍼티` (값 관찰, 방출 모두 가능)
    • Binder: `다른 `
  ControlEvent ControlProperty Binder
읽기 (값 관찰) O O X
쓰기 (값 방출) X O O

ex) ControlEvent

  • 읽기만 가능
// button.rx.tap 정의
extension Reactive where Base: UIButton {
  public var tap: ControlEvent<Void> {
    controlEvent(.touchUpInside)
  }
}

// 사용하는쪽 - 읽기만 가능
button.rx.tap
  .observe(on: MainScheduler.asyncInstance)
  .bind(with: self) { ss, _ in
    print("Tap!")
  }
  .disposed(by: disposeBag)

 ex) ControlProperty

  • 읽기, 쓰기 가능
// textField.rx.text 정의
extension Reactive where Base: UITextField {
  public var text: ControlProperty<String?> {
    value
  }
}

// 사용1 - 쓰기
Observable.just(())
  .observe(on: MainScheduler.asyncInstance)
  .bind(to: textField.rx.text)
  .disposed(by: disposeBag)
  
// 사용2 - 읽기
textField.rx.text
  .observe(on: MainScheduler.asyncInstance)
  .bind(with: self) { ss, _ in
    print(text)
  }
  .disposed(by: disposeBag)

ex) Binder

  • 쓰기만 가능
  • UITextField에 Date 값을 입력하면 포멧에 맞추어서 text에 set 되는 기능
  • Base는 set할 타입을 명시하고, Binder의 제네릭 타입에 주입할 타입을 명시
// 정의
extension Reactive where Base: UITextField {
  var formattedText: Binder<Date> {
    Binder(self.base) { textField, date in
      let dateFormatter = DateFormatter()
      dateFormatter.dateFormat = "YYYY년 MM월 dd일 HH:mm"
      textField.text = dateFormatter.string(from: date)
    }
  }
}

// 사용하는쪽 - 쓰기
Observable.just(())
  .map { Date() }
  .bind(to: textField.rx.formattedText)
  .disposed(by: disposeBag)

 

Comments