RxCocoa剖析


上篇介绍了RxSwift设计理念,核心就是利用函数式编程的理念,构建响应式的程序。可是ios原生自带的框架例如UIKit、UIFoundation等等,都是用命令式的方式编写的,如何适配成响应式的方式。RxSwift框架为此专门开发了一个库叫RxCocoa,这个库的大概设计原理下面就分享下。

基本原理

首先看下RxCocoa框架简略的UML类图,下面会根据这个类图对RxCocoa框架的设计理念做一些分析.

image

首先看下接口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源码中作者画了如下这个图,我们配合这个图来看下其中的原理。

image

作者在源码中说到要扩展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社区能越来越好。

如果你喜欢这篇文章,谢谢你的赞赏

图3

如有疑问请联系我