今天寫這篇文章的目的,是提供一種思路,來幫助大家解決控制器非常臃腫的問題,對控制器瘦身。

如果手邊有項目,不妨打開工程看一下你的控制器代碼有多少行,是不是非常多?再看一下tableView的代理方法cellForRow和heightForRow的代碼是不是也是非常多?里面夾雜著switch和大量if esle的判斷邏輯的代碼。后期維護(hù)看著這些if else是不是特別煩躁?特別是自己在維護(hù)前人寫的代碼,并且還沒有注釋 一團(tuán)糟,是不是有更想罵人的沖動?別怕,這里給您提供一種解決思路,讓你的tableView代理方法再也沒有這種讓人頭疼的if else判斷邏輯,讓你的控制器代碼量大大減少,并且后期維護(hù)成本也大大的減少。
在說具體解決思路前,先給大家簡單復(fù)習(xí)一下MVC和MVVM,因?yàn)榻裉斓闹黝}也是和MVVM有關(guān)系。MVC模式大家都很熟悉了,就是Model,View,Controller三層,Model負(fù)責(zé)數(shù)據(jù)層,Controller負(fù)責(zé)業(yè)務(wù)邏輯層,View負(fù)責(zé)界面顯示層,Model和View通過Controller來實(shí)現(xiàn)橋接交互,程序的擴(kuò)展性很好,好處多多。但是呢,MVC也有它自身的缺陷,那就是控制器太臃腫,如果你想在控制器中定位某一個點(diǎn)是比較麻煩的事。

MVVM中Model依然負(fù)責(zé)數(shù)據(jù)層,Controller單單負(fù)責(zé)View的展示和更新,其他業(yè)務(wù)邏輯不管。View依然負(fù)責(zé)界面顯示。那么ViewController之前負(fù)責(zé)的業(yè)務(wù)邏輯現(xiàn)在誰來負(fù)責(zé)呢?我們再新建一個ViewModel層,處在ViewController層和Model層之間,專門負(fù)責(zé)業(yè)務(wù)邏輯,以及網(wǎng)絡(luò)請求等任務(wù)。ViewController從ViewModel中獲取數(shù)據(jù)然后顯示在View上,它并不和Model層直接打交道,和Model層直接打交道的是ViewModel層 。
下面附上一張經(jīng)典gif圖片,幫助大家理解兩者之間的關(guān)系。

大家可能疑惑到底什么是面向超類編程,其實(shí)就是圍繞繼承這個特性,子類cell繼承父類cell,面向父類這個對象來編程,最終對控制器的tableView進(jìn)行瘦身,也不止是對tableView優(yōu)化,配合MVVM新建ViewModel可以抽離很大一部分控制器的代碼?,F(xiàn)在還不清楚沒關(guān)系,下面會有很詳細(xì)的描述讓你明白。_ 我下面先把大家常用的控制器tableView代理方法的寫法,給黏貼出來,然后再用新的面向超類的寫法給黏貼出來,大家就可以明顯體會到使用面向超類寫法的好處了。


面向超類編程的好處:
1.控制器瘦身。[控制器內(nèi)部代碼量大幅度減少,邏輯更加清晰]
2.后期維護(hù)成本大大降低。[后期如果想添加或者刪除cell,只需要新建或者刪除一個子類cell,在viewModel中添加或刪除一個identifier即可,控制器幾乎不用加任何代碼]
面向超類的壞處:
1.新建更多的cell文件和一個viewModel文件,包大小會響應(yīng)增加。
下面就具體講解面向超類編程瘦身大概要做什么:
一,新建一個繼承自UITableViewCell的父類cell
#import <UIKit/UIKit.h>
#import "ResponseNewProgrammeData.h"
#import "NewProgrammeCellHeightProtocol.h"
//子類需要有回調(diào)事件的代理
@protocol NewProgrammeTableViewCellProtocol <NSObject>
- (void)cell1DidSelectedRightButton;
- (void)cell2DidSelectedRightButton;
- (void)cell3DidSelectedRightButton;
@end
@interface NewProgrammeBaseCell : UITableViewCell <NewProgrammeCellHeightProtocol>
@property (nonatomic, weak) id<NewProgrammeTableViewCellProtocol> delegate;
@property (nonatomic, strong) ResponseNewProgrammeData * responseNewProgrammeData;
@end
首先,要包含控制器的數(shù)據(jù)源,因?yàn)樽宇恈ell的UI等操作全靠這個父類的數(shù)據(jù)源。
其次,要實(shí)現(xiàn)NewProgrammeCellHeightProtocol協(xié)議,作為計算高度用,具體用法在第二點(diǎn)講解。
最后,如果子類cell有點(diǎn)擊事件需要回調(diào)操作的,可再寫一個協(xié)議NewProgrammeTableViewCellProtocol作為屬性持有,在控制器中將delegate指向控制器作為回調(diào)使用。
二,新建一個NewProgrammeCellHeightProtocol
#import <Foundation/Foundation.h>
//針對cell的高度寫的協(xié)議
@protocol NewProgrammeCellHeightProtocol <NSObject>
@optional
+ (BOOL)isStaticCell;
+ (float)cellHeight;
@end
- (BOOL)isStaticCell方法是在子類中使用的,如果當(dāng)前cell是高度固定的靜態(tài)cell,就在返回YES,并且在cellHeight方法中返回固定高度。否則返回NO即可,也不需要寫+ (float)cellHeight方法。這兩個方法會在控制器的heightForRow方法中使用,計算當(dāng)前cell高度。
**三,新建控制器所需要的所有cell,且繼承自剛才的父類cell **
#import "NewProgrammeCell1.h"
@interface NewProgrammeCell1 ()
@property (nonatomic, weak) IBOutlet UILabel *lblName;
@end
@implementation NewProgrammeCell1
@synthesize responseNewProgrammeData = _responseNewProgrammeData;
- (void)setResponseNewProgrammeData:(ResponseNewProgrammeData *)responseNewProgrammeData
{
_responseNewProgrammeData = responseNewProgrammeData;
self.lblName.text = _responseNewProgrammeData.string1;
}
+ (BOOL)isStaticCell
{
return YES;
}
+ (float)cellHeight
{
return 44;
}
- (IBAction)didPressedPush:(id)sender {
if (self.delegate && [self.delegate respondsToSelector:@selector(cell1DidSelectedRightButton)]) {
[self.delegate cell1DidSelectedRightButton];
}
}
@end
這個cell的高度是固定靜態(tài)的,所以isStaticCell方法返回YES,cellHeight返回高度。
這個cell中可以拿到控制器數(shù)據(jù)源,根據(jù)自己的需要去獲取數(shù)據(jù)。
這個cell的didPressedPush方法是模擬需要點(diǎn)擊事件的,回調(diào)給控制器。
其他cell的配置都大致是這樣的。
四,新建viewModel文件
#import "NewProgrammeViewModel.h"
static NSString * const NewProgrammeCell1Identifier = @"NewProgrammeCell1";
static NSString * const NewProgrammeCell2Identifier = @"NewProgrammeCell2";
static NSString * const NewProgrammeCell3Identifier = @"NewProgrammeCell3";
@implementation NewProgrammeViewModel
- (NSArray *)getIdentifierList
{
return @[NewProgrammeCell1Identifier,
NewProgrammeCell2Identifier,
NewProgrammeCell3Identifier];
}
- (void)requestData
{
self.responseNewProgrammeData = [[ResponseNewProgrammeData alloc] init];
}
@end
viewModel負(fù)責(zé)配置控制器所需要注冊的cell以及真正要顯示的cell,getIdentifierList返回需要注冊的所有cell。因?yàn)槟承╉撁娴腸ell不是固定顯示的,可能根據(jù)數(shù)據(jù)源動態(tài)的來配置。同時,viewModel也負(fù)責(zé)網(wǎng)絡(luò)請求數(shù)據(jù)解析等其他業(yè)務(wù)邏輯代碼。這里的viewModel相當(dāng)于MVVM模式中的胖Model,不僅處理網(wǎng)絡(luò)請求,還處理頁面UI的配置等其他業(yè)務(wù)邏輯,這樣就不會使控制器那么臃腫。
五,在控制器中做相應(yīng)代碼配置
- (void)configTableViewCell
{
for (NSString * identifer in [self.viewModel getIdentifierList]) {
[self.tableView registerNib:[UINib nibWithNibName:identifer bundle:nil] forCellReuseIdentifier:identifer];
}
}
#pragma mark - UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.viewModel.getIdentifierList.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * cellIdentifier = [self.viewModel.getIdentifierList objectAtIndex:indexPath.row];
NewProgrammeBaseCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
cell.delegate = self;
cell.responseNewProgrammeData = self.viewModel.responseNewProgrammeData;
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * cellIdentifier = [self.viewModel.getIdentifierList objectAtIndex:indexPath.row];
Class<NewProgrammeCellHeightProtocol> cellClass = NSClassFromString(cellIdentifier);
CGFloat height = 0;
if ([cellClass isStaticCell]) {
height = [cellClass cellHeight];
return height;
} else {
NewProgrammeBaseCell * cell = (NewProgrammeBaseCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath];
height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingExpandedSize].height;
return height;
}
}
首先,要實(shí)例化viewModel對象,在configTableViewCell方法中獲取需要注冊的所有cell,遍歷注冊。
其次,在cellForRow代理方法中,從viewModel對象中獲取所有注冊的cell的identifier,然后從tableView中獲取賦值給父類cell。再針對父類cell做一些賦值操作,也就是分別調(diào)用了子類cell,充分利用多態(tài)的特性。
最后,在heightForRow代理方法中,仍然是根據(jù)viewModel對象獲取所有注冊的cell的identifier,再根據(jù)identifier反射成對象子類cell的類對象,接著調(diào)用cell中的協(xié)議方法,來計算高度。
到這里,面向超類編程瘦身基本思路都說完了,這里有完整的demo點(diǎn)擊下載源碼,如果喜歡動下小手給個star,謝謝啦??~自己可以在項目中嘗試下,可能在實(shí)際應(yīng)用中會有其他沒有想到的問題,比如說,當(dāng)前控制器的cell最多顯示3個,但是在某種情況下是顯示2個,有1個不需要顯示。那么我們在viewModel中應(yīng)該怎么配置getIdentifierList數(shù)據(jù)源呢?我們可以這樣做,獲取最新的數(shù)組。不同的項目業(yè)務(wù)邏輯不一樣,寫法也會有差別,具體問題具體分析。
- (NSArray *)getNewIdentifierList
{
NSMutableArray * newList = [[self getIdentifierList] mutableCopy];
if (**判斷條件**) {
[newList removeObject:NewProgrammeCell2Identifier];
}
return [newList copy];
}
希望本文的瘦身思路可以幫助您,如果此文哪里有紕漏,或者您有什么更好的建議,歡迎提出來,大家一塊探討。iOS開發(fā)技術(shù)交流qq群: 529560119,提供各種最新權(quán)威學(xué)習(xí)書籍及開發(fā)視頻