swift

RxSwift: Binder

kimyounggyun 2022. 8. 5. 20:22

Binder는 UI 바인딩에 쓰는 특별한 Observer이다. 아래와 같은 특징을 가진다.

• 에러를 방출하지 않는다.

Error가 방출되면 Observable의 시퀀스가 끊기기 때문에 UI 업데이트가 종료되기 때문이다.

• 특정 스케줄러를 지정하지 않는 이상 메인 스케줄러에서 동작한다.

UI 업데이트는 메인 스케줄러에서 동작하기 때문이다.

타입

public struct Binder<Value>: ObserverType

ObserverType의 제네릭 구조체이다.

생성자

  • target : Binder가 확장하는 UI 객체 클래스이다. (ex UILabel...)
  • scheduler : 동작할 스케줄러 기본 값이 메인 스케줄러이다.
  • binding : 보통 Value로 전달된 값을 Target에 있는 속성에 저장하는 클로저.
public init<Target: AnyObject>(_ target: Target, 
                                scheduler: ImmediateSchedulerType = MainScheduler(), 
                                binding: @escaping (Target, Value) -> Void) {
    weak var weakTarget = target

    self.binding = { event in
    switch event {
        case .next(let element):
            _ = scheduler.schedule(element) { element in
                if let target = weakTarget {
                    binding(target, element)
                }
                return Disposables.create()
            }
        case .error(let error):
            rxFatalErrorInDebug("Binding error: \(error)")
        case .completed:
            break
        }
    }
}

Next 이벤트

weak var weakTarget = target
case .next(let element):
    _ = scheduler.schedule(element) { element in
        if let target = weakTarget {
            binding(target, element)
        }
        return Disposables.create()
    }

매개 변수로 전달한 스케줄러에서 binding 클로저를 실행한다.

Error 이벤트

case .error(let error):
    rxFatalErrorInDebug("Binding error: \(error)")



func rxFatalErrorInDebug(_ lastMessage: @autoclosure () -> String, 
                        file: StaticString = #file, 
                        line: UInt = #line) {
    #if DEBUG
        fatalError(lastMessage(), file: file, line: line)
    #else
        print("\(file):\(line): \(lastMessage())")
    #endif
}

에러가 방출되지 않은 이유는 그냥 출력하기 때문이다.

Binder 예시

extension Reactive where Base: UILabel {
    var countText: Binder<String> {
        return Binder(self.base) { label, text in
            label.text = "\(text.count)개"
        }
    }
}

Reactive 객체를 extension 하여 Base로 확장할 UI 객체를 지정하면 된다. Binder 객체를 리턴하면 되는데 trailing closure로 bind 함수를 만들면 된다.