結(jié)合單向數(shù)據(jù)流、POP思想,在Swift環(huán)境下對(duì)復(fù)雜TableView部分代碼進(jìn)行優(yōu)化

見過不少人這樣書寫過tableViewDelegate的方法

func numberOfSections(in tableView: UITableView)  -> Int {
     return 2
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
     return 10
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.section == 0 return 50
    if indexPath.section == 1 return 80
    .......還有針對(duì)不同 indexPath.row 返回的
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

     //重點(diǎn)來了,就是這里
    if indexPath.section == 0 {
        if 需要?jiǎng)?chuàng)建A類型的
          /*
          create Acell
          return Acell
          */
        if 需要?jiǎng)?chuàng)建B類型的
          /*
          create Bcell
          return Bcell
          */
        if 需要?jiǎng)?chuàng)建C類型的
          /*
          create Ccell
          return Ccell
          */
    }else{
       ....
       ....
       ....
    }
}

或者是根據(jù)某些條件,如判斷dataSource中對(duì)應(yīng)index的內(nèi)容是什么,然后再去創(chuàng)建cell,不管怎樣,這種寫法的后果就是。。??戳讼胪隆?。。~~!


當(dāng)然,我也經(jīng)歷過讓別人吐這個(gè)階段,沒辦法,雖然在意過但那時(shí)候沒覺得應(yīng)該優(yōu)化,覺得就應(yīng)該這么寫,誰(shuí)讓產(chǎn)品設(shè)計(jì)的頁(yè)面這么復(fù)雜,后期維護(hù)困難時(shí)產(chǎn)品設(shè)計(jì)的問題。。。。

入職現(xiàn)在的公司后,發(fā)現(xiàn)現(xiàn)有小伙伴們正在致力于優(yōu)化代碼,其中的一個(gè)方向就是提高代碼可讀性。。。。這就不可避免的要好好琢磨一下如何優(yōu)化各種 if -else -if、眼花繚亂的 switch-case代碼了。。。。。

經(jīng)過一系列的嘗試,我這里逐漸摸索到了一個(gè)自我感覺比較良好的方式,并利用到平時(shí)的OC開發(fā)中;想在,由于項(xiàng)目需要,正在幫另一個(gè)項(xiàng)目組搞Swift開發(fā),于是就想著是不是應(yīng)該把這個(gè)好東西帶到Swift開發(fā)中,經(jīng)過一番實(shí)踐(沒經(jīng)歷過系統(tǒng)的swift學(xué)習(xí),現(xiàn)在開發(fā)都是直接憑OC的經(jīng)驗(yàn)來的,或者干脆百度copy,所以這個(gè)實(shí)踐的過程特別鬧心)終于成功實(shí)施了。。。

這套方案的核心思想是:

1、利用ViewModel分擔(dān)一部分TableViewDelegate方法

2、數(shù)據(jù)解析階段直接封裝好不同的ViewModel

3、建立協(xié)議,tableView 的 delegator直接通過協(xié)議向viewModel索要cell實(shí)例

一、建立Protocol

import Foundation

@objc protocol WGVMCellProtocol {

//update cell
@objc optional func wg_hl_updateCellInfo(viewModel:AnyObject)
//create a viewModel
@objc optional static func wg_hl_creatViewModel(model:AnyObject) -> (AnyObject)
//create a cell
@objc optional func wg_hl_getSpecificCell(tableView:AnyObject) -> (AnyObject)
//get cell height
@objc optional func wg_hl_getSpecificCellHeight()->(CGFloat)
}

wg_hl_updateCellInfo 是提供給Cell的方法,傳入一個(gè)ViewModel,然后去刷新Cell

wg_hl_creatViewModel 提供給數(shù)據(jù)解析階段,這個(gè)方法可有可無(wú),傳入一個(gè)Model,然后返回一個(gè)ViewModel

wg_hl_getSpecificCell、wg_hl_getSpecificCellHeight,這兩個(gè)方法也是提供給ViewModel去實(shí)現(xiàn)的,目的是讓不同的ViewModel去實(shí)現(xiàn)兩個(gè)代理方法,返回特定的Cell以及Cell的高度

二、創(chuàng)建Cell,并實(shí)現(xiàn)部分協(xié)議

假設(shè)現(xiàn)有3中類型的Cell,一種是理財(cái)產(chǎn)品類型的條目,一種是淘寶上的一款商品的條目,另一種是微博帖子的條目

創(chuàng)建、布局什么的就不說了,只說一下這幾個(gè)Cell必須實(shí)現(xiàn)的protocol方法

extension 理財(cái)Cell:WGVMCellProtocol{

func wg_hl_updateCellInfo(viewModel: AnyObject)   {
   
}
extension 商品Cell:WGVMCellProtocol{

func wg_hl_updateCellInfo(viewModel: AnyObject)   {
   
}
extension 帖子Cell:WGVMCellProtocol{

func wg_hl_updateCellInfo(viewModel: AnyObject)   {
   
}

就是這樣

三、創(chuàng)建不同的ViewModel

同上,只貼上三個(gè)protocol方法的實(shí)現(xiàn)

extension 理財(cái)ViewModel:WGPurchaseProtocol{
    //創(chuàng)建viewModel
    static func wgp_creatViewModel(model: WGRechargeListModel) -> (AnyObject) {
        let viewModel = WGPMyGoldCoinsViewModel.init()
        .....平時(shí)我寫的時(shí)候這里還有好多操作
        return viewModel
    }
    //create a cell
    func wgp_getSpecificCell(tableView:AnyObject) -> (AnyObject){
        //有關(guān)于各種復(fù)用、判空新建的邏輯我寫到Cell里面了,這里直接調(diào)用拿返回
        let cell = WGPMyGoldCoinsTableViewCell.createMyGoldCoinsCell(tableView: tableView as! UITableView)
        return cell
    }

    //get cell height
    func wgp_getSpecificCellHeight()->(CGFloat){
        return 100
    }
}

.......同理,其余兩個(gè)商品和帖子類型的ViewModel也需要實(shí)現(xiàn)這三個(gè)protocol方法

四、數(shù)據(jù)解析時(shí),創(chuàng)建不同的ViewModel,并放入dataSource中提供給tableView

如:在AViewController中,我發(fā)起了數(shù)據(jù)請(qǐng)求,并拿到了原始數(shù)據(jù),之后通過工具將其映射成了對(duì)應(yīng)的Model,現(xiàn)在就用這些Model,來初始化ViewModel:

extension AViewController{
    private func reloadAPI () {
        let _ = AlamofireClient.default.send(requestAPI) { [weak self] (result) in
            if let weak = self {
                switch (result?.status)! {
                case .success:
                    weak.basicModel = result?.respObject as! HLNormalModel
                    let lcVM = 理財(cái)ViewModel.wgp_creatViewModel(model: weak.basicModel!)                      
                    let spVM = 商品ViewModel.wgp_creatViewModel(model: weak.basicModel!)
                    let tzVM = 帖子ViewModel.wgp_creatViewModel(model: weak.basicModel!)
                    weak.dataArr.removeAllObjects()
                    weak.dataArr.add(lcVM)    
                    weak.dataArr.add(spVM)
                    weak.dataArr.add(tzVM)
                    
                    weak.table.reloadData()
                    break;
                    /*
                    這里可能并不是這個(gè)樣子,可能會(huì)面臨著后臺(tái)一個(gè)數(shù)組返回,里面包含著不同的類型條目,這樣的話就沒辦法了,只能根據(jù)某些條件進(jìn)行if判斷,然后創(chuàng)建不同的ViewModel了,或者也可以繼續(xù)用protocol進(jìn)行優(yōu)化,不過我這里沒搞得這么詳細(xì),部分模塊中,這個(gè)地方還是會(huì)出現(xiàn)幾個(gè)if的,不過我覺得沒什么關(guān)系,大不了我讓菊花再多轉(zhuǎn)幾圈唄,反正不會(huì)影響到什么。
                    */
                default:break;
                }
            }  
        }
    }
}

最后一步,ViewController中的實(shí)現(xiàn),或者說tableView的delotor如何實(shí)現(xiàn)

extension AViewController : UITableViewDelegate,UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArr.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let viewModel = dataArr.object(at: indexPath.row) as! WGVMCellProtocol
        return viewModel.wgp_getSpecificCellHeight()
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let viewModel = dataArr.object(at: indexPath.row) as! WGVMCellProtocol
        let cell = viewModel.wgp_getSpecificCell(tableView: tableView) as! WGVMCellProtocol
        cell.wgp_updateCellInfo(viewModel: dataArr.object(at: indexPath.row) as AnyObject)
        return cell as! UITableViewCell
    }

}

總結(jié):

簡(jiǎn)單來講其實(shí)就是建立了一種一對(duì)一的關(guān)系,類似 key:value,讓復(fù)雜的列表邏輯變成這種形式。個(gè)人總結(jié)有以下幾點(diǎn)好處:

1、頁(yè)面可維護(hù)性增強(qiáng),維護(hù)簡(jiǎn)單,增加或者減少某一類型 或者某些類型的Cell條目,只要針對(duì)新條目建立Cell和ViewModel并實(shí)現(xiàn)協(xié)議就可以了,或者通過注釋掉創(chuàng)建某個(gè)類型ViewModel來控制某種條目不展示

2、列表滾動(dòng)流暢度有所提升,畢竟不需要再動(dòng)態(tài)的if-else來決定創(chuàng)建哪一個(gè)Cell了,這部分的計(jì)算時(shí)間被優(yōu)化掉了,而菊花轉(zhuǎn)動(dòng)3圈和5圈用戶是沒有強(qiáng)烈感知的,只要不是耗時(shí)性操作,解析成ViewModel很快的,當(dāng)然了,除非你謝了10以上的if-else才有可能出現(xiàn)可感知的體驗(yàn)提升

3、還可以通過給Protocol建立擴(kuò)展,分擔(dān)一部分業(yè)務(wù),或者將Cell中的具體實(shí)現(xiàn)轉(zhuǎn)移到協(xié)議的extension中,將代碼業(yè)務(wù)粒度進(jìn)一步變小

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容