开启生长之旅!这是我参加「日新方案 12 月更文挑战」的第2天,点击检查活动概况

一、RxCocoa底层原理

简介

RxCocoa是RxSwift的一部分,主要是UI相关的Rx封装。比方完成了很多组件的绑定功用,能够把值跟控件之间彼此绑定,能够避免写很多告诉、修改数据等代码。也能够监听delegate改动,无须把控件创立及delegate处理分开写等。

RxCocoa里边也定义了很多类,专门为UI处理提供的,比方ControlPropertyControlEventDriverBinder等。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)
  }

打印结果:

iOS探索RxCocoa底层原理

看下分组代码:

    /// 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)
  }

iOS探索RxCocoa底层原理
咱们发现这个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 这应该便是设置署理,持续进入源码:

iOS探索RxCocoa底层原理
那这个中介署理是怎样完成的呢,进入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) 从头设置署理.

iOS探索RxCocoa底层原理
终于找到了,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 检查源码:

iOS探索RxCocoa底层原理
就看到一个Binder和闭包绑定数据,改写UI,on一个音讯。咱们进入它的父类TableViewSectionedDataSource
iOS探索RxCocoa底层原理
持续分析源码:

#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的署理回调呢?在那呢?

iOS探索RxCocoa底层原理
找到了_RxTableViewReactiveArrayDataSource
iOS探索RxCocoa底层原理
查找承继联系 _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检查:

iOS探索RxCocoa底层原理
如果methodInvoked呼应到系统的tabeView( :didSelectRowAt:)就会执行到对应的map函数中去。 这样就把tableview的全部流程rx音讯分发完毕。

总结

RxCocoa底层原理最中心的,便是经过delegateProxy -> setCurrentDelegate(proxy, toObject: object) 中介者模式从头封装了tableview的delegate和datasoucre 而且把回调办法,也在RxTableViewReactiveArrayDataSource中完成封装,在经过tableView.rx.itemSelected 订阅音讯->methodInvoked(监听系统事情呼应)进行map音讯分发。大幅度的减少了原生tableview的代码完成。