RxSwift 很是強大,用得好,很爽git
場景舉例,就是兩表關聯。 RxDataSource,怎樣列表刷新出來,就自動選擇第一個。而後子列表根據上一個列表的選擇,確認要刷新的數據。編程
左邊一個列表 tableView, 右邊一個列表 tableView , 兩個列表之間的數據存在關聯,左邊列表是一級選項,右邊列表是對應左邊的二級選項。bash
private var lastIndex : NSInteger = 0
// ...
// 看這一段代碼,就夠了
let leftDataSource = RxTableViewSectionedReloadDataSource<CategoryLeftSection>( configureCell: { [weak self] ds, tv, ip, item in
guard let strongSelf = self else { return UITableViewCell()}
let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
cell.model = item
// 看這一句代碼,就夠了
if ip.row == strongSelf.lastIndex {
// ...
tv.selectRow(at: ip, animated: false, scrollPosition: .top)
tv.delegate?.tableView!(tv, didSelectRowAt: ip)
}
return cell
})
vmOutput!.sections.asDriver().drive(self.leftMenuTableView.rx.items(dataSource: leftDataSource)).disposed(by: rx.disposeBag)
// 選擇左邊列表,給右邊提供的數據
let rightPieceListData = self.leftMenuTableView.rx.itemSelected.distinctUntilChanged().flatMapLatest {
[weak self](indexPath) -> Observable<[SubItems]> in
guard let strongSelf = self else { return Observable.just([]) }
strongSelf.currentIndex = indexPath.row
if indexPath.row == strongSelf.viewModel.vmDatas.value.count - 1 {
// ...
strongSelf.leftMenuTableView.selectRow(at: strongSelf.currentSelectIndexPath, animated: false, scrollPosition: .top)
strongSelf.leftMenuTableView.delegate?.tableView!(strongSelf.leftMenuTableView, didSelectRowAt: strongSelf.currentSelectIndexPath!)
return Observable.just((strongSelf.currentListData)!)
}
if let subItems = strongSelf.viewModel.vmDatas.value[indexPath.row].subnav {
// ...
var reult:[SubItems] = subItems
// ...
strongSelf.currentListData = reult
strongSelf.currentSelectIndexPath = indexPath
return Observable.just(reult)
}
return Observable.just([])
}.share(replay: 1)
// 右邊列表的數據源,具體 cell 的配置方法
let rightListDataSource = RxTableViewSectionedReloadDataSource<CategoryRightSection>( configureCell: { [weak self]ds, tv, ip, item in
guard let strongSelf = self else { return UITableViewCell() }
if strongSelf.lastIndex != strongSelf.currentIndex {
tv.scrollToRow(at: ip, at: .top, animated: false)
strongSelf.lastIndex = strongSelf.currentIndex
}
if ip.row == 0 {
let cell :CategoryListBannerCell = CategoryListBannerCell()
cell.model = item
return cell
} else {
let cell : CategoryListSectionCell = tv.dequeueReusableCell(withIdentifier: "Cell2", for: ip) as! CategoryListSectionCell
cell.model = item
return cell
}
})
// 設置右邊列表的代理
rightListTableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
// 右邊列表須要取得的數據,傳遞給右邊列表的配置方法
rightPieceListData.map{ [CategoryRightSection(items:$0)] }.bind(to: self.rightListTableView.rx.items(dataSource: rightListDataSource))
.disposed(by: rx.disposeBag)
複製代碼
上面這個實現很 Low, 選擇的時機是這個 if ip.row == strongSelf.lastIndex {
, 當更新數據到指定 cell 時候,操做。數據結構
程序能夠跑,可是不優雅。閉包
若是把上面的邏輯翻譯成面向對象,就是:ide
open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
cell.model = item
// 看這一句代碼,就夠了
if ip.row == strongSelf.lastIndex {
// ...
tv.selectRow(at: ip, animated: false, scrollPosition: .top)
tv.delegate?.tableView!(tv, didSelectRowAt: ip)
// ...
}
return cell
}
複製代碼
這樣展開,清晰了一些。咱們是不會這麼用的,若是不用 Rx, 直接 OOP. 咱們是這麼用 刷新完了以後,選中一下函數
tableView.reloadData()
tv.selectRow(at: ip, animated: false, scrollPosition: .top)
複製代碼
// 其餘操做優化
open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
cell.model = item
return cell
}
複製代碼
leftDataSource
經過泛型指明每一個 tableView section 的數據結構,他惟一的參數是一個閉包, configureCell .ui
let rightListDataSource = RxTableViewSectionedReloadDataSource<CategoryLeftSection>( configureCell: { [weak self] ds, tv, ip, item in
// ...
}
複製代碼
configureCell 實際上就是系統提供的代理方法 open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
spa
RxDataSource 的源代碼挺清晰的,首先採用前提條件 precondition
, row 確保不會越界。而後調用 configureCell
匿名函數。這樣的設計,借用了 Swift 中函數是一級公民,函數能夠像值同樣傳遞。
open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
precondition(indexPath.item < _sectionModels[indexPath.section].items.count)
return configureCell(self, tableView, indexPath, self[indexPath])
}
複製代碼
繼承 TableViewSectionedDataSource
, 建立本身想要的子類,任意根據需求改方法。
作一個繼承
final class MyDataSource<S: SectionModelType>: RxTableViewSectionedReloadDataSource<S> {
private let relay = PublishRelay<Void>()
var rxRealoded: Signal<Void> {
return relay.asSignal()
}
override func tableView(_ tableView: UITableView, observedEvent: Event<[S]>) {
super.tableView(tableView, observedEvent: observedEvent)
// Do diff
// Notify update
relay.accept(())
}
}
複製代碼
由於這個場景下, super.tableView(tableView, observedEvent: observedEvent)
, 調用以後,須要接受一下事件,之後把它發送出去。因此要用一個 PublishSubject
. PublishRelay
是對 PublishSubject
的封裝,區別是他的功能沒 PublishSubject
那麼強,PublishRelay
少兩個狀態,完成 completed 和出錯 error, 適合更加專門的場景。
Signal
信號嘛,共享事件流 SharedSequence。他是對 Observable
的封裝。具備在主線程調用等特性,更適用於搞 UI .
簡單優化下,代碼語義更加明確
// left menu 數據源
let leftDataSource = MyDataSource<CategoryLeftSection>( configureCell: { ds, tv, ip, item in
let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
cell.model = item
return cell
})
// 刷新完了,作一下選擇
leftDataSource.rxRealoded.emit(onNext: { [weak self] in
guard let self = self else { return }
let indexPath = IndexPath(row: 0, section: 0)
self.leftMenuTableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableView.ScrollPosition.none)
self.leftMenuTableView.delegate?.tableView?(self.leftMenuTableView, didSelectRowAt: indexPath)
}).disposed(by: rx.disposeBag)
// ...
// 其他不變
複製代碼
FRP 就是把要幹嗎,直接寫清楚,不會像 OOP 那樣,過分的見縫插針,調用到了,就改一下狀態。
聲明式編程是隻在一處修改。就算把那一處修改,聲明式編程也只在一處調用。OOP 就找的比較辛苦。
git repo , 我放在 coding.net 上面了,pod 都裝好了,下載了直接跑