返回首页

RxSwift 自定义 Delegate 实现

最近在学习 RxSwift, 一个很常用的例子就是通过 UITableView 各种数据的展示和事件的点击来学习。通常我们会通过原生的 delegate 来控制各种行为,比如通过 tableView(_:didSelectRowAt:) 这个方法来设置点击 cell 之后的自动反选,但既然是在学习 Reactive programming, 我们期待实现下面的效果:

myTableView.rx.didSelectedRowAtIndexPath.subscribe(onNext: {
  // do something
}

这看上去更简洁,更符合 RxSwift 的习惯,RxSwift 也提供了这种机制:DelegateProxy, 让我们可以实现上面的代码。

事实上,RxSwift 为我们提供了一个 itemSelected 变量可以作为我们学习的对象

tableView.rx.itemSelected.subscribe(onNext: { path in 
//do something
}

通过查看 itemSelected 在 UITableView+Rx.swift 的源码我们可以看到,它其实就是 delegate message tableView:didSelectRowAtIndexPath: 的 Reactive wrapper, 其中 source 是通过 delegate.methodInvoked 返回的 Observable

public var itemSelected: ControlEvent<IndexPath> {
        let source =  self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:)))
            .map { a in
                return try castOrThrow(IndexPath.self, a[1])
            }

        return ControlEvent(events: source)
    }

那这就解释的通,对 itemSelected 订阅来获取通知的方法。我们继续查看 delegate 定义

 /// Reactive wrapper for `delegate`.
 /// For more information take a look at `DelegateProxyType` protocol documentation.
  public var delegate: DelegateProxy<UIScrollView, UIScrollViewDelegate> {
            return RxScrollViewDelegateProxy.proxy(for: base)
        }

可以看到 delegate 是原生 delegate 的 Reactive wrapper, 是一个继承了 DelegateProxy 的 computed property, 并且通过查资料得知,只能通过 proxyForObject 方法来创建它,然后我们继续了解 DelegateProxy 这个协议, 具体源码略长,这里就不附上,但我们了解到,它是 DelegateProxyType 的基本实现,并且了解到想要实现这个协议所必须实现的三个方法,我们试着自己定义自己的 RxDelegate:

class MyRxTableViewDelegateProxy: DelegateProxy<UITableView, UITableViewDelegate>, UITableViewDelegate, DelegateProxyType {
    static func registerKnownImplementations() {
        self.register {
            MyRxTableViewDelegateProxy(parentObject: $0, delegateProxy: MyRxTableViewDelegateProxy.self) }
    }
    
    static func currentDelegate(for object: UITableView) -> UITableViewDelegate? {
        return object.delegate
    }
    
    static func setCurrentDelegate(_ delegate: UITableViewDelegate?, to object: UITableView) {
        object.delegate = delegate
    }
}

需要注意的几点是:

  1. 自定义协议遵从 DelegateProxy, DelegateProxyType,和我们想要模仿的 UITableViewDelegate 三个协议

  2. 让 delegate proxy 获取原生delegate对象:

```
    static func currentDelegate(for object: UITableView) -> UITableViewDelegate? {
        return object.delegate
    }
```

设置 delegate proxy 对象

```
    static func setCurrentDelegate(_ delegate: UITableViewDelegate?, to object: UITableView) {
        object.delegate = delegate
    }
```

用于向RxCocoa注册我们自定义的DelegateProxyType

```
        self.register {
            MyRxTableViewDelegateProxy(parentObject: $0, delegateProxy: MyRxTableViewDelegateProxy.self) }
    }
```

然后我们需要定义 rxDidSelectedRowAtIndexPath 和它的对象 rxDelegate, 依据源码我们可以很容易试着对 UITableView 扩展

extension UITableView {
    var rxDelegate: MyRxTableViewDelegateProxy {
        return MyRxTableViewDelegateProxy.proxy(for: self)
    }
    
        var rxDidSelectRowAtIndexPath: Observable<(UITableView, IndexPath)> {
      return rxDelegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:))) .map{ params in
   
        return (params[0] as! UITableView, params[1] as! IndexPath)
    }

而对于 Selector 方法,一个更好的写法是:

private extension Selector {
    static let didSelectRowAt =
        #selector(UITableViewDelegate.tableView(_:didSelectRowAt:))
}

所以我们的 rxDidSelectRowAtIndexPath 就变成了:

    var rxDidSelectRowAtIndexPath: Observable<(UITableView, IndexPath)> {
        return rxDelegate.methodInvoked(.didSelectRowAt).map { params in
            return (params[0] as! UITableView, params[1] as! IndexPath)
        }
    }

至此,我们自定义的 DelegateProxy 就可以进行原生 delegate 的功能了:

self.myTableView.rxDidSelectRowAtIndexPath.subscribe(onNext: {
            $0.deselectRow(at: $1, animated: true)
       }).disposed(by: bag)

SilkSong

Programmer. Open source & AI enthusiast, currently learning to build AI agents.

订阅更新