apple/UIKit & ReactiveX
[Swift] extension Reactive
lgvv
2022. 1. 12. 17:24
✅ 이번 시간에는 extension Reactive에 대해서 알아볼 예정이야.
우리가 설치한 RxSwfit 안에는 Reactive.swift 파일이 있는데, custom point로 사용하라고 한다.
🟠 Reactive.swift
Use `Reactive` proxy as customization point for constrained protocol extensions.
General pattern would be:
// 1. Extend Reactive protocol with constrain on Base
// Read as: Reactive Extension where Base is a SomeType
extension Reactive where Base: SomeType {
// 2. Put any specific reactive extension for SomeType here
}
With this approach we can have more specialized methods and properties using
`Base` and not just specialized on common base type.
*/
public struct Reactive<Base> {
/// Base object to extend.
public let base: Base
/// Creates extensions with base object.
///
/// - parameter base: Base object.
public init(_ base: Base) {
self.base = base
}
}
✅ 그렇다면 Reactive 이미 구현된 RxSwift 파일에서는 어떻게 사용하고 있을까?
extension Reactive where Base: UIButton {
/// Reactive wrapper for `TouchUpInside` control event.
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}
이런식으로 구현하고 있다.
그럼 우리도 이에 맞춰서 custom하게끔 구현할 수 있겠지?
✅ 본격적으로 들어가기 전에... 알아야 할 3가지
1️⃣ ControlEvent - 값을 관찰할 수는 있으나, 값을 주입시키지는 못함.
2️⃣ ControlProperty - 값을 주입시킬 수도 있고, 값의 변화도 관찰할 수 있다.
3️⃣ Binder - 값을 주입시킬 수 있으나, 값의 변화는 관찰하지 못함.
🟠 UIViewController+Rx
import Foundation
import RxCocoa
import RxSwift
extension Reactive where Base: UIViewController {
var viewWillAppear: ControlEvent<Void> {
let source = self.methodInvoked(#selector(Base.viewWillAppear(_:))).map { _ in }
return ControlEvent(events: source)
}
}
사용법은 아래와 같이 사용할 수 있다.
rx.viewWillAppear // UIViewController+Rx에 정의
.asObservable() // 반환되는 이벤트를 옵저버블 타입으로 받는다. (공식문서 link에 의하면 From으로 연결되어 있음)
.bind(to: viewWillAppearSubject)
.disposed(by: disposeBag)
🟠 UIButton+Rx.swift
import Foundation
import RxCocoa
import RxSwift
extension Reactive where Base: UIButton {
// 같은 기능을 재사용하기 위한 목적!
public var customChangeTitle: Binder<String> {
let s = Binder<String>(base) { btn, text in
btn.setTitle(text, for: [])
}
return s
} // 같은 기능을 재사용하기 위한 목적!
}
위 아래 두 코드는 target이 base임으로 같은 기능을 한다.
extension Reactive where Base: UIButton {
// 같은 기능을 재사용하기 위한 목적!
public var customChangeTitle: Binder<String> {
let s = Binder<String>(base) { _, text in
base.setTitle(text, for: [])
}
return s
}
}
Binder<String>(base, binding: {}) // 클로저 구문 이렇게도 작성 가능
어떤 일을 할 수 있냐면
myButton.rx
.tap
.map{ "text" }
.bind(to: myButton.rx.customChangeTitle)
.disposed(by: bag)
이렇게 해서 사용할 수 있어. 함수가 사라져서 훨씬 좋은 구조
🟠 UITextField+Rx.swift
import Foundation
import RxCocoa
import RxSwift
import UIKit
extension Reactive where Base: UITextField {
public var text: ControlProperty<String?> {
value
}
}
ControlProperty는 값을 주입할 수 있기 때문에
textField.rx.text.onNext("Hello") // 값을 주입할 수 있기 때문에
textField.rx.text.subcribe(onNext: { _ in }) // 값을 관찰할 수 있기 때문에
✅ Binder의 원형에 대해서 살펴보자.
extension으로 Binder를 사용하려고 하는 경우에는 target을 받아야 한다.
그래서 아까 Button에서 같은 기능을 하는 것도 이와 같은 이유에서이다.