开启生长之旅!这是我参加「日新方案 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
,还有比如itemDeselected
、itemAccessoryButtonTapped
、itemInserted
、itemDeleted
、itemMoved
等等一系列对应 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)
}
打印结果:
看下分组代码:
/// 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)
}
咱们发现这个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
这应该便是设置署理,持续进入源码:
那这个中介署理是怎样完成的呢,进入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)
从头设置署理.
终于找到了,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 检查源码:
就看到一个Binder和闭包绑定数据,改写UI,on一个音讯。咱们进入它的父类TableViewSectionedDataSource
:
持续分析源码:
#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的署理回调呢?在那呢?
找到了_RxTableViewReactiveArrayDataSource
查找承继联系 _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
检查:
如果methodInvoked
呼应到系统的tabeView( :didSelectRowAt:)就会执行到对应的map函数中去。
这样就把tableview的全部流程rx音讯分发完毕。
总结
RxCocoa底层原理最中心的,便是经过delegateProxy -> setCurrentDelegate(proxy, toObject: object)
中介者模式从头封装了tableview的delegate和datasoucre
而且把回调办法,也在RxTableViewReactiveArrayDataSource
中完成封装,在经过tableView.rx.itemSelected 订阅音讯->methodInvoked
(监听系统事情呼应)进行map音讯分发。大幅度的减少了原生tableview的代码完成。