扩展UI组件时常用到的一些发布者与订阅者如下:

发布者:

  • ControlEvent(专门用于描述 UI 控件所产生的事件)

订阅者(观察者):

  • Binder(专门用于绑定UI状态的,如:当某个状态改变时,更新UI状态)

既是发布者又是订阅者:

  • ControlProperty(专门用于描述 UI 控件属性的,如:为属性赋值、订阅属性值变化)

为UI控件扩展RX能力时,一般都是对Reactive进行扩展,如下:为UIView扩展backgroundColor状态绑定

extension Reactive where Base: UIView {
var backgroundColor: Binder<UIColor?> {
Binder(base){ (v: UIView, color: UIColor?) in
v.backgroundColor = color
}
}
}

这里采用的是swift的条件扩展,Base是Reactive的泛型类型,当Base为UIView时,为Reactive扩展backgroundColor计算属性,属性类型为Binder(观察者,用于观察外部状态变化,从而更新视图背景色)

目前Reactive内部实现了动态成员查找,我们不再需要为类的属性分别扩展状态绑定,先看一下Reactive内部是如何实现的:

@dynamicMemberLookup
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
} /// Automatically synthesized binder for a key path between the reactive
/// base and one of its properties
public subscript<Property>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Property>) -> Binder<Property> where Base: AnyObject {
Binder(self.base) { base, value in
base[keyPath: keyPath] = value
}
}
}

关于动态成员查找@dynamicMemberLookup的描述,可以参考这篇文章:@dynamicMemberLookup(动态成员查找)

UIControl视图组件的扩展

RxCocoa为我们扩展了UIControl,它提供了以下两个方法:

A public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()>

B public func controlProperty<T>(
editingEvents: UIControl.Event,
getter: @escaping (Base) -> T,
setter: @escaping (Base, T) -> Void
) -> ControlProperty<T>

方法A返回一个ControlEvent类型的发布者,它专门用于发出UIControl触发的事件,如点击事件。

方法B返回一个ControlProperty类型的(发布者&订阅者),它既可以用于发出UIControl触发的事件,又可以绑定状态变化,从而改变UIControl的状态。

因此有了上面的两个方法,我们就可以轻松实现UIControl类型视图的事件处理与状态绑定。

下面是RxCocoa为我们实现的UIButton的tap事件发布者:

extension Reactive where Base: UIButton {

    /// Reactive wrapper for `TouchUpInside` control event.
public var tap: ControlEvent<Void> {
controlEvent(.touchUpInside)
}
}

有时有的视图组件某个属性既需要绑定状态,又需要可以发出某些事件的情况,针对这种情况我们就需要用到ControlProperty类型来扩展UI属性。

下面是一个UITextField扩展text属性的实现,它是RxCocoa提供的:

extension Reactive where Base: UITextField {
/// Reactive wrapper for `text` property.
public var text: ControlProperty<String?> {
value
} /// Reactive wrapper for `text` property.
public var value: ControlProperty<String?> {
return base.rx.controlPropertyWithDefaultEvents(
getter: { textField in
textField.text
},
setter: { textField, value in
// This check is important because setting text value always clears control state
// including marked text selection which is important for proper input
// when IME input method is used.
if textField.text != value {
textField.text = value
}
}
)
} /// Bindable sink for `attributedText` property.
public var attributedText: ControlProperty<NSAttributedString?> {
return base.rx.controlPropertyWithDefaultEvents(
getter: { textField in
textField.attributedText
},
setter: { textField, value in
// This check is important because setting text value always clears control state
// including marked text selection which is important for proper input
// when IME input method is used.
if textField.attributedText != value {
textField.attributedText = value
}
}
)
}
}

因为UITextField继承自UIControl,因此我们可以利用之前对UIControl扩展的方法,快速为UITextField添加text:ControlProperty属性。controlPropertyWithDefaultEvents:就是UIControl扩展提供的方法,它默认指定的controlEvents为:[.allEditingEvents, .valueChanged],即:当UITextField触发所有编辑事件和值发生改变时,会对外发出这些事件。

RxCocoa为我们还提供了以下继承自UIControl的UI组件:

  • UITextFiled.text、UITextField.value、UITextField.attributedText(ControlProperty类型)
  • UITextView.text、UITextView.value、UITextView.attributedText(ControlProperty类型)
  • UISlider.value(ControlProperty类型)

  • UIStepper.value(ControlProperty类型)

  • UISwitch.isOn、UISwitch.value(ControlProperty类型)

  • UIDatePicker.date、UIDatePicker.value、UIDatePicker.countDownDuration(ControlProperty类型)

  • UISegmentedControl.selectedSegmentIndex、UISegmentedControl.value(ControlProperty类型)

Selector方法Hook扩展

有时我们需要对UI组件的某个方法调用增加监听,例如监听UIViewController的viewDidLoad方法被调用后,加载页面数据。RxCocoa为我们提供了两个方法,来实现对实例方法的hook:

public func sentMessage(_ selector: Selector) -> Observable<[Any]>

public func methodInvoked(_ selector: Selector) -> Observable<[Any]>

这两个方法唯一的区别就是,sentMessage会在selector方法调用前,发出消息,消息内容为selector方法的参数;而methodInvoked会在selector方法调用后,发出消息,消息内容为selector方法参数。

这两个方法返回Observable<[Any]>,我们可以对它进行订阅,从而在方法selector调用前后,处理一些逻辑。

使用这两个方法对自定义的方法进行Hook时需要注意、注意、注意:自定义的方法前必须使用@objc dynamic标注,否则Hook无效。这里与KVO的使用注意项类似

这里简单举个例子:

@objc dynamic // 这里是必须的
private func login(name: String, pwd: String) -> Bool {
guard name == "aaa", pwd == "bbb" else {
return false
}
return true
} // Hook
rx.sentMessage(#selector(login(name:pwd:))).map({$0.map(String.init(describing:))}).bind { (params: [String]) in
print("call login before, userName: \(params[0]), password: \(params[1])")
}.disposed(by: disposeBag)

这里推荐一个UIViewController的rx扩展,它提供了viewDidLoad、viewWillAppear等方法的Hook:RxViewController

对于处理方法Hook的发布者我们一般采用ControlEvent,因此RxViewController.viewDidLoad的返回值类型为:ControlEvent,我们可以看一下具体是如何扩展一个viewDidLoad的:

extension Reactive where Base: UIViewController {

    var viewDidLoad: ControlEvent<Void> {
// 这里采用methodInvoked,意味着会在viewDidLoad方法调用后,发出事件消息。如果你需要在ViewDidLoad方法调用前发出消息,你可以选择使用sentMessage实现
let obsed = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in}
return ControlEvent<Void>(events: obsed)
} }

Delegate的方法Hook扩展

RxCocoa实现Delegate的hook的核心原理,看下面的一张图就很容易理解:

RxCocoa通过代理的代理,实现方法转发,将实际的调用者转发给forwardToDelegate,并且实现了在forwardToDelegate调用方法前后增加了_sentMessage:withArguments:和_methodInvoked:withArguments:的调用。(注意:只有当前的方法返回值为void时,这两个方法才会调用)

RxCocoa提供一个DelegateProxy类,它继承自_RXDelegateProxy,因此DelegateProxy具有代理转发功能,即:代理的代理。

DelegateProxy它重写了上面两个方法:

  • _sentMessage:withArguments:
  • _methodInvoked:withArguments:

这两个方法会通过selector获取对应要触发的绑定函数,这些函数存储在当前类DelegateProxy的以下属性中:

  • _sentMessageForSelector:[Selector:MessageDispatcher]
  • _methodInvokedForSelector:[Selector:MessageDispatcher]

并且DelegateProxy还提供如下方法来为代理方法添加绑定函数,将绑定函数保存在以上两个属性中:

  • sentMessage(_ selector: Selector) -> Observable<[Any]>
  • methodInvoked(_ selector: Selector) -> Observable<[Any]>

一般为代理添加RX扩展,就会用到上面这两个方法。

另外还有一个非常重要的协议:DelegateProxyType,这个协议用于注册代理的代理,它提供了如下两个方法:

  • static func register<Parent>(make: @escaping (Parent) -> Self)
  • static func proxy(for object: ParentObject) -> Self

register:用于初始化代理的代理,并保存在sharedFactory中

proxy:用于获取上面注册的代理的代理

因此要实现自定义Delegate的rx扩展,你需要如下几个步骤:

  • 定义个代理的代理类,使之继承DelegateProxy。例如:CustomDelegateProxy:DelegateProxy
  • 让上面的代理的代理实现自定义协议,如果代理的方法是可选的,你可以不用实现。例如:CustomDelegateProxy:CustomDelegate
  • 让上面的代理的代理实现DelegateProxyType协议,实现注册代理类方法,以及设置原代理方法。
  • 在CustomDelegateProxy中通过sentMessage或methodInvoked定义相关方法的RX扩展即可

这里举个例子:

// 代理
@objc
protocol CustomActionSheetDelegate: NSObjectProtocol { func didSelectItem(item: String)
@objc optional func didCancel()
} // 代理的代理类
class CustomActionSheetDelegateProxy:
DelegateProxy<CustomActionSheet, CustomActionSheetDelegate>,
DelegateProxyType,
CustomActionSheetDelegate{ private(set) var actionSheet: CustomActionSheet? init(actionSheet: CustomActionSheet) {
self.actionSheet = actionSheet
super.init(parentObject: actionSheet, delegateProxy: CustomActionSheetDelegateProxy.self)
} static func registerKnownImplementations() {
// 注册代理类
register(make: {CustomActionSheetDelegateProxy(actionSheet: $0)})
} static func currentDelegate(for object: CustomActionSheet) -> CustomActionSheetDelegate? {
object.delegate
} static func setCurrentDelegate(_ delegate: CustomActionSheetDelegate?, to object: CustomActionSheet) {
object.delegate = delegate
} private var _selectItemSubject: PublishSubject<String>?
var innerSelectItemSubject: PublishSubject<String> {
if let sub = _selectItemSubject {
return sub
}
let sub = PublishSubject<String>()
_selectItemSubject = sub
return sub
} // 实现CustomActionSheetDelegate方法
// 由于该方法被实现,因此执行代理方法时,不会走方法转发,因此当设置了原代理时
// 原代理的方法也就不会被执行,因此下面方法增加了对forwardToDelegate的判断
func didSelectItem(item: String) {
if let delegate = forwardToDelegate(), delegate.responds(to: #selector(didSelectItem(item:))) {
delegate.didSelectItem(item: item)
}
innerSelectItemSubject.onNext(item) // 发送选择消息
} deinit{
if let sub = _selectItemSubject {
sub.on(.completed) // 结束订阅
}
} }

添加Reactive扩展:

// 扩展RX
extension Reactive where Base: CustomActionSheet { // 对外提供代理的代理
var delegate: DelegateProxy<CustomActionSheet, CustomActionSheetDelegate>{
CustomActionSheetDelegateProxy.proxy(for: base)
} // 设置原代理的方法
func setDelegate(_ delegate: CustomActionSheetDelegate) -> Disposable {
CustomActionSheetDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: base)
} // 新增选择rx扩展
var didSelectItem: ControlEvent<String> {
// 因为代理的代理类,实现了didSelectItem方法,因此这里不能通过delegate.methodInvoked进行绑定
// delegate.methodInvoked(#selector(CustomActionSheetDelegate.didSelectItem(item:)))
// 需要如下方式:
let source = CustomActionSheetDelegateProxy.proxy(for: base).innerSelectItemSubject
return ControlEvent(events: source)
} // 新增取消rx扩展
var cancel: ControlEvent<Void> {
let source = delegate.methodInvoked(#selector(CustomActionSheetDelegate.didCancel)).map({_ in})
return ControlEvent(events: source)
}
}

以上只是处理一些没有返回值的代理方法,上面提到过,当代理方法有返回值的时候,sentMessage和methodInvoked无法正确执行,因此我们无法通过这两个方法添加绑定。

一般带有返回值的代理我们叫它DataSource,在RxCocoa中实现DataSource原理如下:

  • 创建一个DataSource的实现类,例如:RxTableViewReactiveArrayDataSource: UITableViewDataSource
  • 为实现类添加初始化方法,参数:一个用于返回TableViewCell的闭包函数
  • 在实现类中增加Items,用于存储数据源
  • 实现类再次实现RxTableViewDataSourceType协议
  • 在Reactive扩展中,新增items函数,参数为:RXTableViewDataSource & UITableViewDataSource,返回值为函数:(ObservableType) -> Disposable,它可以用于bind(to:)函数绑定。

我们看一下items的函数:

public func items<
DataSource: RxTableViewDataSourceType & UITableViewDataSource,
Source: ObservableType>
(dataSource: DataSource)
-> (_ source: Source)
-> Disposable
where DataSource.Element == Source.Element {
return { source in
// This is called for side effects only, and to make sure delegate proxy is in place when
// data source is being bound.
// This is needed because theoretically the data source subscription itself might
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
// setting data source will set delegate, and UITableView might get into a weird state.
// Therefore it's better to set delegate proxy first, just to be sure.
_ = self.delegate
// Strong reference is needed because data source is in use until result subscription is disposed
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource as UITableViewDataSource, retainDataSource: true) { [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in
guard let tableView = tableView else {
return
}
dataSource.tableView(tableView, observedEvent: event)
}
}
}

该方法中完成了对ObservableType的观察绑定,如:source.subscribeProxyDataSource,绑定闭包中调用了DataSource的dataSource.tableView(tableView, observedEvent: event)

而这个方法的实现如下:

func tableView(_ tableView: UITableView, observedEvent: Event<Sequence>) {
Binder(self) { tableViewDataSource, sectionModels in
let sections = Array(sectionModels)
tableViewDataSource.tableView(tableView, observedElements: sections)
}.on(observedEvent)
}

这里再次调用了自己的tableView(tableView, observedElements: sections)函数,如下:

func tableView(_ tableView: UITableView, observedElements: [Element]) {
self.itemModels = observedElements tableView.reloadData()
}

因此当我们调用Observable.just(["first item", "second item", "third item"]).bind(to: tableView.rx.items(dataSource))时,items实现了对Observable的订阅,在订阅中调用DataSource的tableView(tableView, observedEvent: event),然后将event中的Element,即:["first item", "second item", "third item"]赋值给DataSource中的属性:items,然后调用tableView.reloadData(),然后执行DataSource中的代理回调,如下:

func numberOfSections(in tableView: UITableView) -> Int {
1
} func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
} func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let element = items[indexPath.row]
configCell(tableView, indexPath, element) // 这里是DataSource初始化时传入的闭包函数
}

这样就完成了对DataSource数据源代理的绑定,下面是具体的原理图解:

Target-Action的RX绑定

RxCocoa针对target-action进行了统一的封装,这里的target-action指的是通过addTarget:action:方法来实现UI事件绑定的方法。

例如:所有继承自UIControl,UIGestureRecognizer

RxCocoa提供了一个统一的RxTarget类,作为addTarget:action:中的target,而这个类巧妙的运用了循环引用,来控制自己的生命周期,并且它实现了Disposable协议,在dispose方法中将循环引用断开,从而释放资源。下面是RxTarget的具体定义:

class RxTarget: NSObject, Disposable {

    private var retainSelf: RxTarget? // 循环引用,用于控制自己的生命周期

    override init() {
super.init()
self.retainSelf = self
} func dispose() {
self.retainSelf = nil // 断开循环引用
}
}

针对UIControl和UIGestureRecognizer RxCocoa提供了以下两个target类:

  • ControlTarget
  • GestureTarget

这两个类都继承自RxTarget,目的是为了控制自己的生命周期。并且它们在各自的初始化方法中实现了addTarget:action:的绑定,并提供一个闭包函数,来处理action的回调。

这里我们拿ControlTarget举例,以下是源码实现:

final class ControlTarget: RxTarget {
typealias Callback = (UIControl) -> Void let selector: Selector = #selector(ControlTarget.eventHandler(_:)) weak var control: UIControl? var callback: Callback?
let controlEvents: UIControl.Event init(control: UIControl, controlEvents: UIControl.Event, callback: @escaping Callback) { self.control = control
self.callback = callback
self.controlEvents = controlEvents
super.init() // 绑定target与action
control.addTarget(self, action: selector, for: controlEvents)
} @objc func eventHandler(_ sender: UIControl!) {
if let callback = self.callback, let control = self.control {
callback(control)
}
} override func dispose() { // 解除绑定
super.dispose()
self.control?.removeTarget(self, action: self.selector, for: self.controlEvents)
self.callback = nil
}
}

文章上面我们提到过UIControl视图组件的扩展,其中提供了如下方法:

public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()>

public func controlProperty<T>(
editingEvents: UIControl.Event,
getter: @escaping (Base) -> T,
setter: @escaping (Base, T) -> Void
) -> ControlProperty<T>

而UIGestureRecognizer提供了如下扩展:

extension Reactive where Base: UIGestureRecognizer {

    /// Reactive wrapper for gesture recognizer events.
public var event: ControlEvent<Base> {
let source: Observable<Base> = Observable.create { [weak control = self.base] observer in
MainScheduler.ensureRunningOnMainThread() guard let control = control else {
observer.on(.completed)
return Disposables.create()
} let observer = GestureTarget(control) { control in
observer.on(.next(control))
} return observer
}.take(until: deallocated) return ControlEvent(events: source)
} }

这样我们就可以通过如下方式绑定手势的事件:

let tap = UITapGestureRecognizer()
tap.rx.event.bind { _ in
print("tap gesture call")
}.disposed(by: disposeBag)

了解了以上RxCocoa的实现原理,我们就可以为我们自己的UI组件添加RxCocoa扩展了。RxSwift社区为我们实现了很多功能,我们可在这里查看到社区中的贡献项目。

最新文章

  1. 利用win服务定时为网卡启用/禁用
  2. 自定义MapReduce的类型
  3. 当编译CCBReader时出现 “ CCBAnimationManager.m Use of undeclared identifier &#39;other‘ ” 解决方法
  4. wine install 32bit netframewok
  5. 1st-code-review summary
  6. Poj(1469),二分图最大匹配
  7. 在KVM虚拟机中使用spice系列之二(USB映射,SSL,密码,多客户端支持)
  8. openerp binary filed import export
  9. IIS ,未能加载文件或程序集“System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。系统找不到指定的文件。
  10. HDU 3081 Marriage Match II(二分法+最大流量)
  11. iOS中 为 iOS 建立 Travis CI 韩俊强的博客
  12. MQTT入手笔记
  13. 已实现乐观锁功能,FreeSql.DbContext 准备起航
  14. java关于redis的快速配置
  15. VSCode下调试mocha测试用例
  16. php递归操作目录 递归对参数转义
  17. 用户定义的java计数器
  18. javascript: break跳出多重循环以及退出each循环
  19. 安装PYthon+Kivy环境(记录)
  20. hdoj2612 Find a way (bfs)

热门文章

  1. Lodop打印小票
  2. 亲测有效! Super PhotoCut Pro 超级抠图工具 V2.8.8 for mac 破解版
  3. 与ChatGPT关于测试问题的对话
  4. JZOJ 4216.平方和
  5. JZOJ 5347. 【NOIP2017提高A组模拟9.5】遥远的金字塔
  6. 内网安全之:MS14-068 Kerberos 域用户提权漏洞
  7. ASP.NET Core - 依赖注入(三)
  8. 一文搞懂│http 和 https 的通信过程及区别
  9. 《HelloTester》第2期
  10. ChatGpt聊天API使用