iOS探索RxCocoa底层原理

3,382 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

一、RxCocoa底层原理

简介

RxCocoa是 RxSwift 的一部分,主要是UI相关的Rx封装。比如实现了很多组件的绑定功能,可以把值跟控件之间相互绑定,可以避免写很多通知、修改数据等代码。也可以监听delegate改变,无须把控件创建及delegate处理分开写等。

RxCocoa里面也定义了很多类,专门为UI处理提供的,比如ControlProperty、 ControlEvent 、 Driver 、Binder等。RxCocoa可以用好的话,可以极大简化UI相关处理逻辑。但是,要想随心所欲的使用,还是要对其实现要有一定的了解,否则就容易写出不是那么简洁的代码。

1.代理转发

我们通过一个tableDemo来学习探索RxCocoa底层原理:

NYTableViewCell 的代码

class NYTableViewCell: UITableViewCell{

    let disposeBag = DisposeBag()
    //头像视图
    lazy var iconView: UIImageView = {

        let imageView = UIImageView.init()
        imageView.contentMode = UIView.ContentMode.scaleAspectFill
        imageView.isUserInteractionEnabled = true
        return imageView
    }()
    

    //用户昵称
    lazy var nameLabel: UILabel = {
        let nameLab = UILabel()
        nameLab.font = UIFont.systemFont(ofSize: 15)
        return nameLab

    }()

    //负责课程
    lazy var classLabel: UILabel = {

       let classLab = UILabel()
        classLab.font = UIFont.systemFont(ofSize: 14)
        classLab.textColor = UIColor.gray
        return classLab
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupUI()
        setupPhotoBrowser()
    }

    required init?(coder aDecoder:NSCoder){
        fatalError("init(coder:) has not been implemented")
    }

    /// UI 添加
    func setupUI(){

        contentView.addSubview(iconView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(classLabel)
        let iconViewSize = CGSize(width: 44, height: 44)
        let leftMargin   = 20;

        iconView.snp.makeConstraints { make in

            make.left.equalTo(leftMargin)
            make.centerY.equalToSuperview()
            make.size.equalTo(iconViewSize)
        }

        nameLabel.snp.makeConstraints { make in
            make.top.equalTo(iconView)
            make.left.equalTo(iconView.snp.right).offset(leftMargin/2)
        }

        classLabel.snp.makeConstraints { make in

            make.bottom.equalTo(iconView)
            make.left.equalTo(nameLabel)
            make.right.equalTo(-leftMargin)
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {

        super.setSelected(selected, animated: animated)
        // Configure the view for the selected state
    }

    func setUIData(_ model:NYModel){

        iconView.image = UIImage.init(named: model.name)
        nameLabel.text = model.name
        classLabel.text = model.className
        let iconViewSize = CGSize(width: 44, height: 44)
        iconView.ny_roundCorner(cornerRadii: iconViewSize.width/2)
    }
    
    func setUISectionData(_ model:NYSectionModel) {
        iconView.image = model.image
        nameLabel.text = model.name
        classLabel.text = model.gitHubID

        let iconViewSize = CGSize(width: 44, height: 44)
        iconView.ny_roundCorner(cornerRadii: iconViewSize.width/2)
    }

    func setupPhotoBrowser(){
        /// 给图片添加手势
        let tapGesture = UITapGestureRecognizer()
        iconView.addGestureRecognizer(tapGesture)
        tapGesture.rx.event.subscribe({ event in
            let browser = JXPhotoBrowser()
            browser.numberOfItems = { 1 }
            // 数据源
            browser.reloadCellAtIndex = { context in
                let browserCell = context.cell as? JXPhotoBrowserImageCell
                let image1: UIImage!
                if let image = UIImage(named: self.classLabel.text!) {
                    image1 = image
                } else {
                    image1 = UIImage(named: self.nameLabel.text!)
                }
                browserCell?.imageView.image = image1
            }
            browser.show()
        }).disposed(by: self.disposeBag)
    }
}

未使用rx.delegate tableview代码:

        let tabView = UITableView.init(frame: view.bounds, style: .plain)
        tabView.delegate = self
        tabView.dataSource = self
        tabView.tableFooterView = UIView()
        tabView.register(NYTableViewCell.classForCoder(), forCellReuseIdentifier: reuseID)
        
        /// 事件代理
        extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let rxVC = NYRxViewController()
        navigationController?.pushViewController(rxVC, animated: true)
    }
}

一般不会去直接使用 delegate,譬如要处理 tableView 的点击事件,我们会这样:tableView.rx.itemSelected.subscribe(onNext: handleSelectedIndexPath),这跟先设置一个 delegate,然后在 delegate 的tableView(_:didSelectRowAt:)方法中调用handleSelectedIndexPath的效果是一样的。那这个过程到底是如何进行的呢?我们进入 RxCocoa 的 UITableView+Rx.swift 文件来一探究竟,这个文件中不仅有itemSelected,还有诸如itemDeselecteditemAccessoryButtonTappeditemInserteditemDeleteditemMoved等等一系列对应 tableView delegate 的包装方法.

我们通过如下例子探索RxCocoa的底层原理:

    /// 核心代码
    /// tableView -- RX
    func setupTableViewRX() {
        let dataOB = BehaviorSubject.init(value: self.viewModel.dataArray)

        // Rx实现
        dataOB.bind(to: self.tableView.rx.items(cellIdentifier: reuseID, cellType: NYTableViewCell.self)){ row , model, cell in
            cell.setUIData(model as! NYModel)
        }.disposed(by: disposeBag)

        // tableView点击事件
        tableView.rx.itemSelected.subscribe(onNext:{ [weak self]indexPath in
            print("点击\(indexPath)行")
            self?.navigationController?.pushViewController(NYSectionViewController(), animated: true)
            self?.tableView.deselectRow(at: indexPath, animated: true)
        }).disposed(by: disposeBag)

        // tableView复选点击事件
        tableView.rx.itemDeselected.subscribe(onNext:{ indexPath in
            print("再次点击\(indexPath)行")
        }).disposed(by: disposeBag)

        // tableView移动事件
        tableView.rx.itemMoved.subscribe(onNext: { [weak self] sourceIndex, destinationIndex in
            print("从\(sourceIndex)移动到\(destinationIndex)")
            self?.viewModel.dataArray.swapAt(sourceIndex.row, destinationIndex.row)
            self?.loadUI(obSubject: dataOB)
        }).disposed(by: disposeBag)
        
        // tableView删除事件
        tableView.rx.itemDeleted.subscribe(onNext: { [weak self]indexPath in
            print("点击删除\(indexPath)行")
            self?.viewModel.dataArray.remove(at: indexPath.row)
            self?.loadUI(obSubject: dataOB)
        }).disposed(by: disposeBag)

        
        // tableView新增事件
        tableView.rx.itemInserted.subscribe(onNext: { [weak self]indexPath in
            print("添加数据在\(indexPath)行")
            guard let model = self?.viewModel.dataArray.last else {
                print("数据有问题,无法新增")
                return
            }
            self?.viewModel.dataArray.insert(model, at: indexPath.row)
            self?.loadUI(obSubject: dataOB)
        }).disposed(by: disposeBag)

    }

打印结果:

image.png

看下分组代码:

    /// tableView懒加载
    lazy var tableView: UITableView = {
        let tabView = UITableView.init(frame: self.view.bounds, style: .plain)
        tabView.tableFooterView = UIView()
        tabView.register(NYTableViewCell.classForCoder(), forCellReuseIdentifier: reuseID)
        tabView.rowHeight = 80
        return tabView
    }()
    /// Rx 处理分组
    func setupTableViewRX() {

        let tableViewDataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,NYSectionModel>>(configureCell: { [weak self]dataSource, tab, indexPath,model -> NYTableViewCell in

            let cell = tab.dequeueReusableCell(withIdentifier: self?.reuseID ?? "reuseID_NYSectionViewController", for: indexPath) as! NYTableViewCell
            cell.setUISectionData(model)
            cell.selectionStyle = .none
            return cell

        }, titleForHeaderInSection: { dataSource, index -> String in
            return dataSource.sectionModels[index].model
        })

        data.githubData.asDriver(onErrorJustReturn: [])
            .drive(self.tableView.rx.items(dataSource: tableViewDataSource))
            .disposed(by: self.disposeBag)
    }

image.png 我们发现这个tabelView并没有设置代理,可是能正常显示为什么呢?进入源码代码:

public func items<
            DataSource: RxTableViewDataSourceType & UITableViewDataSource,
            Source: ObservableType>
        (dataSource: DataSource)
        -> (_ source: Source)
        -> Disposable
        where DataSource.Element == Source.Element {
        return { source in
            _ = 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)
            }
        }
    }

看到了_ = self.delegate 这应该就是设置代理,继续进入源码: image.png 那这个中介代理是怎么实现的呢,进入RxTableViewDataSourceProxy.proxy

//核心代码
    if currentDelegate !== delegateProxy {
            delegateProxy._setForwardToDelegate(currentDelegate, retainDelegate: false)
            assert(delegateProxy._forwardToDelegate() === currentDelegate)
            self._setCurrentDelegate(proxy, to: object)
            assert(self._currentDelegate(for: object) === proxy)
            assert(delegateProxy._forwardToDelegate() === currentDelegate)
        }

如果当前currentDelegate不等于delegateProxy 就self._setCurrentDelegate(proxy, to: object) 重新设置代理. image.png 终于找到了,object=tableview 给tableview设置了代理。

那么另一个datasoucre的代理呢?怎么没在这里?

回到上面的代码:source.subscribeProxyDataSource订阅了数据源的代理。找到关键代码

let unregisterDelegate = DelegateProxy.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object)

//在进入源码查看
public static func installForwardDelegate(_ forwardDelegate: Delegate, retainDelegate: Bool, onProxyForObject object: ParentObject) -> Disposable {

        weak var weakForwardDelegate: AnyObject? = forwardDelegate as AnyObject

        let proxy = self.proxy(for: object)//这句是关键
  
        proxy.setForwardToDelegate(forwardDelegate, retainDelegate: retainDelegate)
        //....................省略代码.......................//
}

//再次进入self.proxy(for: object)
public static func proxy(for object: ParentObject) -> Self {

        MainScheduler.ensureRunningOnMainThread()

        //....................省略代码.......................//

        if currentDelegate !== delegateProxy {
            delegateProxy._setForwardToDelegate(currentDelegate, retainDelegate: false)
            assert(delegateProxy._forwardToDelegate() === currentDelegate)
            self._setCurrentDelegate(proxy, to: object)
            assert(self._currentDelegate(for: object) === proxy)
            assert(delegateProxy._forwardToDelegate() === currentDelegate)
        }
        return delegateProxy
    }

// self._setCurrentDelegate(proxy, to: object) -> ParentObject.DataSource setCurrentDelegate  这里写的很高明
extension DelegateProxyType where ParentObject: HasDataSource, Self.Delegate == ParentObject.DataSource {
    public static func currentDelegate(for object: ParentObject) -> Delegate? {
        object.dataSource
    }
    public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
        object.dataSource = delegate
    }
}

这里有看到了熟悉的代码 delegateProxy :DelegateProxyType 实现DelegateProxyType 协议的中介者来控制 delegate 和 datasoucre的实现。同一个self._setCurrentDelegate(proxy, to: object)分别设置了 delegate 和 datasoucre的代理。(这里确实有点难理解)

然后进入RxTableViewSectionedReloadDataSource 查看源码: image.png 就看到一个Binder和闭包绑定数据,刷新UI,on一个消息。我们进入它的父类TableViewSectionedDataSourceimage.png 继续分析源码:

#if os(iOS)
        public init(
                configureCell: @escaping ConfigureCell,
                titleForHeaderInSection: @escaping  TitleForHeaderInSection = { _, _ in nil },
                titleForFooterInSection: @escaping TitleForFooterInSection = { _, _ in nil },
                canEditRowAtIndexPath: @escaping CanEditRowAtIndexPath = { _, _ in true },
                canMoveRowAtIndexPath: @escaping CanMoveRowAtIndexPath = { _, _ in true },
                sectionIndexTitles: @escaping SectionIndexTitles = { _ in nil },
                sectionForSectionIndexTitle: @escaping SectionForSectionIndexTitle = { _, _, index in index }
            ) {
            self.configureCell = configureCell
            self.titleForHeaderInSection = titleForHeaderInSection
            self.titleForFooterInSection = titleForFooterInSection
            self.canEditRowAtIndexPath = canEditRowAtIndexPath
            self.canMoveRowAtIndexPath = canMoveRowAtIndexPath
            self.sectionIndexTitles = sectionIndexTitles
            self.sectionForSectionIndexTitle = sectionForSectionIndexTitle
        }
    #else

init 初始化的时候ConfigureCell必须配置需要显示的cell,其他参数有默认的空实现可以不填。

那tableview的代理回调呢?在那呢? image.png 找到了_RxTableViewReactiveArrayDataSource image.png 搜索继承关系 _RxTableViewReactiveArrayDataSource 找到了具体的子类实现。

// Please take a look at `DelegateProxyType.swift`
class RxTableViewReactiveArrayDataSource<Element>
    : _RxTableViewReactiveArrayDataSource
    , SectionedViewDataSourceType {
    typealias CellFactory = (UITableView, Int, Element) -> UITableViewCell

    //....................省略代码.......................//

    let cellFactory: CellFactory
    
    init(cellFactory: @escaping CellFactory) {
        self.cellFactory = cellFactory  //通过工厂cell 得到具体的cell 闭包,也就是configureCell
    }

    override func _tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        itemModels?.count ?? 0  //模型数据
    }

    override func _tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        cellFactory(tableView, indexPath.item, itemModels![indexPath.row])
    }

    // reactive
    func tableView(_ tableView: UITableView, observedElements: [Element]) {
        self.itemModels = observedElements
        tableView.reloadData() //刷新UI
    }

}

那么itemModels如何订阅事件呢?进入tableView.rx.itemSelected.subscribe查看: image.png 如果methodInvoked响应到系统的tabeView( :didSelectRowAt:)就会执行到对应的map函数中去。 这样就把tableview的全部流程rx消息分发完毕。

总结

RxCocoa底层原理最核心的,就是通过delegateProxy -> setCurrentDelegate(proxy, toObject: object) 中介者模式重新封装了tableview的delegate和datasoucre 并且把回调方法,也在RxTableViewReactiveArrayDataSource中实现封装,在通过tableView.rx.itemSelected 订阅消息->methodInvoked(监听系统事件响应)进行map消息分发。大幅度的减少了原生tableview的代码实现。