上篇介绍了RxSwift设计理念,核心就是利用函数式编程的理念,构建响应式的程序。可是ios原生自带的框架例如UIKit、UIFoundation等等,都是用命令式的方式编写的,如何适配成响应式的方式。RxSwift框架为此专门开发了一个库叫RxCocoa,这个库的大概设计原理下面就分享下。
基本原理
首先看下RxCocoa框架简略的UML类图,下面会根据这个类图对RxCocoa框架的设计理念做一些分析.
首先看下接口ReactiveCompatible,如果你查看RxCocoa的源码会看到下面的代码
/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }
因为ios的基础UI框架都是继承NSObject,所以也就是让所有的UI框架都扩展了RxSwift的功能。
接下来我们再看下Reative这个结构体的实现,我们查看下源码
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
}
}
/// A type that has reactive extensions.
public protocol ReactiveCompatible {
/// Extended type
associatedtype ReactiveBase
@available(*, deprecated, message: "Use `ReactiveBase` instead.")
typealias CompatibleType = ReactiveBase
/// Reactive extensions.
static var rx: Reactive<ReactiveBase>.Type { get set }
/// Reactive extensions.
var rx: Reactive<ReactiveBase> { get set }
}
可以看出来,实现ReactiveCompatible协议的类,都会包含一个Reative对象rx,并且Reative是一个模板类,会有一个Base类型,这个类型就是ios基础库的类型了。接下来,我们就看下UIView这个类是怎么扩展成具备RxSwift的响应式功能的。
extension Reactive where Base: UIButton {
/// Reactive wrapper for `TouchUpInside` control event.
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}
可以看到通过这种方法就很容易的扩展UIButton点击事件为一个RxSwift的Observal了。写按钮响应事件的时候就可以用如下代码.
let button = UIButton(type: .custom)
button.rx.tap.subscribe(onNext: { _ in
})
这样就很方便的扩展ios各种控件,下面再来看下UIViewController的一些扩展方法
extension Reactive where Base: UIViewController {
/// Bindable sink for `title`.
public var title: Binder<String> {
return Binder(self.base) { viewController, title in
viewController.title = title
}
}
}
Binder这个又是什么,看下源码实现
public struct Binder<Value>: ObserverType
本质上是Observer类型,作为观察者对象,也就是Binder是可以接收事件响应的方法。例如下面的用法
let observal = Single<String>.create { single in
single(.success("Title"))
return Disposables.create { }
}.asObservable()
observal.asObservable().bind(to:viewController.rx.title)
所以对于常用的控件,可以用上述方法扩展控件的属性为Observal和Observer类型。这样既可以作为输出的源又可以作为输入的源,基本就可以用响应式的方式进行编程了。
再仔细想想,我们可能漏了什么东西,在UITableView,UICollectionView这里控件,有大量的delegate和datasource方法,那么控件里的delegate和datasouce方法又是如何转换成RxSwift的方法那?下面我们就来讲解下,可能是比上面的方法要略微复杂些。
下面我们看文章开头的UML类图右边的结构,你会发现DelegateProxy这个类是实现代理方法转换成RxSwift方法的核心。而这个类其实是实现了DelegateProxyType的协议,我们就看下这个协议的核心代码
public protocol DelegateProxyType: class {
associatedtype ParentObject: AnyObject
associatedtype Delegate
/// It is require that enumerate call `register` of the extended DelegateProxy subclasses here.
static func registerKnownImplementations()
/// Unique identifier for delegate
static var identifier: UnsafeRawPointer { get }
/// Returns designated delegate property for object.
///
/// Objects can have multiple delegate properties.
///
/// Each delegate property needs to have it's own type implementing `DelegateProxyType`.
///
/// It's abstract method.
///
/// - parameter object: Object that has delegate property.
/// - returns: Value of delegate property.
static func currentDelegate(for object: ParentObject) -> Delegate?
/// Sets designated delegate property for object.
///
/// Objects can have multiple delegate properties.
///
/// Each delegate property needs to have it's own type implementing `DelegateProxyType`.
///
/// It's abstract method.
///
/// - parameter toObject: Object that has delegate property.
/// - parameter delegate: Delegate value.
static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject)
/// Returns reference of normal delegate that receives all forwarded messages
/// through `self`.
///
/// - returns: Value of reference if set or nil.
func forwardToDelegate() -> Delegate?
/// Sets reference of normal delegate that receives all forwarded messages
/// through `self`.
///
/// - parameter forwardToDelegate: Reference of delegate that receives all messages through `self`.
/// - parameter retainDelegate: Should `self` retain `forwardToDelegate`.
func setForwardToDelegate(_ forwardToDelegate: Delegate?, retainDelegate: Bool)
}
RxCocoa源码中作者画了如下这个图,我们配合这个图来看下其中的原理。
作者在源码中说到要扩展UIScrollViewDelegate的方法需要定义一个DelegateType类型,这个类型中通过ProxyFactory来创建,本质上是调用DelegateType的注册方法registerKnownImplementations。为了让delegate的方法具备Observable能力,在DelegateType对象里面定义了一个PublishSubject的对象,然后把delegate中的方法存储到了一个队列中,如果delegate方法被调用,PublishSubject对象就会响应此事件。这样delegateType就具备了Observal的能力。
通过上面的方法,你会发现,当创建DelegateType时,需要把控件所有的代理方法实现一遍。作者为了不增加这么多冗余的代码,特意写了_RXDelegateProxy这个oc语言编写的类,主要是为了用runtime的方法,因为swift没有runtime特性,所以作者也只能勉强为之了。下面看下这个类runtime的方法实现。
-(void)forwardInvocation:(NSInvocation *)anInvocation {
BOOL isVoid = RX_is_method_signature_void(anInvocation.methodSignature);
NSArray *arguments = nil;
if (isVoid) {
arguments = RX_extract_arguments(anInvocation);
[self _sentMessage:anInvocation.selector withArguments:arguments];
}
if (self._forwardToDelegate && [self._forwardToDelegate respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self._forwardToDelegate];
}
if (isVoid) {
[self _methodInvoked:anInvocation.selector withArguments:arguments];
}
}
如果熟悉runtime就会发现,本质上就是把ios控件代理的方法用forwardInvocation方式捕获掉,当用户用rxswift方式调用代理的方法时,forwardInvocation会转发给DelegateProxy类型,从而实现了捕获,不过作者也说了,这里Runtime只实现了没有参数返回的代理方法,如果有参数返回,比如UICollectionView的dataSource都会返回参数,这时就需要自己定义方法实现了,例如下面RxCollectionViewDataSourceProxy的实现
open class RxCollectionViewDataSourceProxy
: DelegateProxy<UICollectionView, UICollectionViewDataSource>
, DelegateProxyType
, UICollectionViewDataSource {
/// Typed parent object.
public weak private(set) var collectionView: UICollectionView?
/// - parameter collectionView: Parent object for delegate proxy.
public init(collectionView: ParentObject) {
self.collectionView = collectionView
super.init(parentObject: collectionView, delegateProxy: RxCollectionViewDataSourceProxy.self)
}
// Register known implementations
public static func registerKnownImplementations() {
self.register { RxCollectionViewDataSourceProxy(collectionView: $0) }
}
private weak var _requiredMethodsDataSource: UICollectionViewDataSource? = collectionViewDataSourceNotSet
// MARK: delegate
/// Required delegate method implementation.
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (_requiredMethodsDataSource ?? collectionViewDataSourceNotSet).collectionView(collectionView, numberOfItemsInSection: section)
}
/// Required delegate method implementation.
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return (_requiredMethodsDataSource ?? collectionViewDataSourceNotSet).collectionView(collectionView, cellForItemAt: indexPath)
}
/// For more information take a look at `DelegateProxyType`.
open override func setForwardToDelegate(_ forwardToDelegate: UICollectionViewDataSource?, retainDelegate: Bool) {
_requiredMethodsDataSource = forwardToDelegate ?? collectionViewDataSourceNotSet
super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate)
}
}
RxCocoa使用
下面就先感受下RxCocoa的便捷之处吧,只需要几行代码就可以绑定数据源到CollectionView,并且不需要额外定义任何全局变量和方法。
let items = Observable.just([
1,
2,
3
])
items.bind(to: collectionView.rx.items) { (collectionView, row, element) in
let indexPath = IndexPath(row: row, section: 0)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.numberLabel.text = "\(element) @ \(row)"
return cell
}
.disposed(by: disposeBag)
下面我们来逐行分析下如何通过两行代码实现CollectionView的DataSource的。首先看下Observal对象中bind这个方法
public func bind<R1, R2>(to binder: (Self) -> (R1) -> R2, curriedArgument: R1) -> R2 {
return binder(self)(curriedArgument)
}
本质上来讲是把一个闭包参数,封装成一个Binder,然后接收Observal的事件响应,那我们看下传递的闭包collectionView.rx.items到底又是个什么结构,代码如下:
public func items<Sequence: Swift.Sequence, Source: ObservableType>
(_ source: Source)
-> (_ cellFactory: @escaping (UICollectionView, Int, Sequence.Element) -> UICollectionViewCell)
-> Disposable where Source.Element == Sequence {
return { cellFactory in
let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper<Sequence>(cellFactory: cellFactory)
return self.items(dataSource: dataSource)(source)
}
}
从上面的方法可以看出来items会接受一个闭包的参数,然后传递给RxCollectionViewReactiveArrayDataSourceSequenceWrapper这个类,它会把cellFactory存储下来。然后调用self.items(dataSource: dataSource)(source)这个函数,参数是一个Observal类型也就是事件源和RxCollectionViewReactiveArrayDataSourceSequenceWrapper对象,接下来我们看下返回的函数是什么
public func items<
DataSource: RxCollectionViewDataSourceType & UICollectionViewDataSource,
Source: ObservableType>
(dataSource: DataSource)
-> (_ source: Source)
-> Disposable where DataSource.Element == Source.Element
{
return { source in
// This is called for sideeffects 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 UICollectionView 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, retainDataSource: true) { [weak collectionView = self.base] (_: RxCollectionViewDataSourceProxy, event) -> Void in
guard let collectionView = collectionView else {
return
}
dataSource.collectionView(collectionView, observedEvent: event)
}
}
}
这里就非常清晰了,把source事件源,绑定到了刚才接收参数闭包的类里面,也就是items = Observable.just([1,2,3])每发射一个元素,都会调用我们传递的闭包参数,同时传递给之前讲到的DelegateType实现的DataSource的代理事件,这样完整的数据源就传递给了UICollectionView的datasource。
下面代码是扩展了UIImagePickerController这个类,让其具备RxSwift的能力,其实上面在讲解原理的时候已经分析过了,如何扩展ios基础控件具备RxSwift能力,下面就简单说下。
public class RxImagePickerDelegateProxy :
DelegateProxy<UIImagePickerController,
UIImagePickerControllerDelegate & UINavigationControllerDelegate>,
DelegateProxyType,
UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
public init(imagePicker: UIImagePickerController) {
super.init(parentObject: imagePicker,
delegateProxy: RxImagePickerDelegateProxy.self)
}
public static func registerKnownImplementations() {
self.register { RxImagePickerDelegateProxy(imagePicker: $0) }
}
public static func currentDelegate(for object: UIImagePickerController)
-> (UIImagePickerControllerDelegate & UINavigationControllerDelegate)? {
return object.delegate
}
public static func setCurrentDelegate(
_ delegate: (UIImagePickerControllerDelegate
& UINavigationControllerDelegate)?,
to object: UIImagePickerController) {
object.delegate = delegate
}
}
extension Reactive where Base: UIImagePickerController {
public var pickerDelegate: DelegateProxy<UIImagePickerController,
UIImagePickerControllerDelegate & UINavigationControllerDelegate > {
return RxImagePickerDelegateProxy.proxy(for: base)
}
public static func showPicker(_ controller:UIViewController) -> Observable<UIImagePickerController> {
let picker = UIImagePickerController()
let barTitleDict = [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18, weight: .medium),
NSAttributedString.Key.foregroundColor: UIColor.white
]
picker.navigationBar.titleTextAttributes = barTitleDict
picker.navigationBar.tintColor = UIColor.white
picker.navigationBar.barStyle = .black
picker.navigationBar.backgroundColor = UIColor.background
let observal = Observable<UIImagePickerController>.create { (obsever) -> Disposable in
picker.modalPresentationStyle = .fullScreen
picker.sourceType = .photoLibrary
controller.present(picker, animated: true, completion: nil)
let dismissDisposable = Observable
.merge(picker.rx.didFinishPickingMediaWithInfo.map { _ in () },picker.rx.didCancel)
.subscribe(onNext: { _ in
obsever.on(.completed)
})
obsever.onNext(picker)
return Disposables.create(dismissDisposable, Disposables.create {
picker.dismiss(animated: true, completion: nil)
})
}
return observal
}
public var didFinishPickingMediaWithInfo: Observable<[UIImagePickerController.InfoKey : AnyObject]> {
return pickerDelegate
.methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:)))
.map({ (a) in
return try castOrThrow(Dictionary<UIImagePickerController.InfoKey, AnyObject>.self, a[1])
})
}
public var didCancel: Observable<()> {
return pickerDelegate
.methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerControllerDidCancel(_:)))
.map {_ in () }
}
}
private func castOrThrow<T>(_ resultType: T.Type, _ object: Any) throws -> T {
guard let returnValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return returnValue
}
首先我们扩展了Reative使UIImagePickerController具备RxSwift能力。其次我们要创建一个Proxy的类,这个类要实现DelegateProxyType和所有的UIImagePickerController用到的delegate。创建这个类的时候,记得要实现registerKnownImplementations方法,这样就可以自动实现Proxy类的创建了,由于UIImagePickerController的代理方法都没有返回参数,所以我们就不用自定义这些代理方法了,直接用下面的方法就可以扩展所有的代理响应了。
pickerDelegate
.methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:)))
这样我们就把一个不具备RxSwift能力的UIImagePickerController类,扩展为了具备RxSwift能里的类,就可以方便的使用响应式编程了。
总结
从上面的讲解可以看出,RxCocoa基本扩展了ios所有控件的属性,每个方法都可以用RxSwift响应式的方式编程。自此就可以感受到RxSwift代码的便捷和优雅了。不过虽然说RxSwift可以快捷的绑定数据到UI上,但是也有一些弊端。比如在做响应式编程的时候,由于一般很多第三方库都是命令式的方法,需要写大量的方法进行扩展,以适配响应式编程的方式。还有一个弊端就是用RxSwift写的项目往往在调试的时候,堆栈信息会过长,不太利于调试。不过RxSwift整个编程的思想源于事件驱动,对于客户端UI数据的绑定尤其有用,Rx本质上也是函数式编程的一种思想,将来在服务器开发方面,并发编程的优势也会体现出来。希望Rx社区能越来越好。