見過不少人這樣書寫過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)一步變小