RxDataSource 使用套路與解釋

RxSwift 很是強大,用得好,很爽git

套路: tableView 刷新之後,就是有了數據源,怎樣來一個回調。

場景舉例,就是兩表關聯。 RxDataSource,怎樣列表刷新出來,就自動選擇第一個。而後子列表根據上一個列表的選擇,確認要刷新的數據。編程

問題是 RxDataSource 專一於列表視圖的數據處理,自動選擇第一個 row 是 tableViewDelegate 乾的事情。

output.png

左邊一個列表 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 都裝好了,下載了直接跑

相關文章
相關標籤/搜索