ViewController的瘦身技術

ViewController中的代碼量一般都是很大, 而且其中包含了許多沒必要要的代碼. 因此ViewController中代碼的複用率一般都是最低的, 接下來會介紹幾種技術對ViewController進行瘦身處理, 讓代碼變得能夠複用, 將代碼移動到合適的地方.objective-c

把 Data Source 和其餘 Protocols 分離出來

把UITableViewDataSource的代碼提出來放到一個單獨的類中, 是爲ViewController進行瘦身的一項強大技術.數組

舉個例子, 在項目中會有個PhotoViewController類, 它有如下的一些方法來爲tableView提供數據.緩存

# pragma mark ---

- (Photo*)photoAtIndexPath:(NSIndexPath*)indexPath {
    return photos[(NSUInteger)indexPath.row];
}

- (NSInteger)tableView:(UITableView*)tableView
 numberOfRowsInSection:(NSInteger)section {
    return photos.count;
}

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
    PhotoCell* cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier
                                                      forIndexPath:indexPath];
    Photo* photo = [self photoAtIndexPath:indexPath];
    cell.label.text = photo.name;
    return cell;
}
複製代碼

能夠看到上面的代碼都是圍繞數組在作事情, 更仔細地說, 是圍繞PhotoViewController所管理的photos數組作一些事情. 咱們能夠將這些與數組相關的代碼移動到單獨的類中. 而後對於cell的具體內容設置, 咱們可使用代理或者block.markdown

@implementation ArrayDataSource

- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
    return items[(NSUInteger)indexPath.row];
}

- (NSInteger)tableView:(UITableView*)tableView
 numberOfRowsInSection:(NSInteger)section {
    return items.count;
}

- (UITableViewCell*)tableView:(UITableView*)tableView
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
    id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                              forIndexPath:indexPath];
    id item = [self itemAtIndexPath:indexPath];
    configureCellBlock(cell,item);
    return cell;
}

@end
複製代碼

如今咱們將與dataSource都放到了單獨的類ArrayDataSource中, 而且讓ArrayDataSource遵循UITableViewDataSource協議. 以後咱們在ViewController中就能夠把UITableViewDataSource所需實現的三個方法去掉, 取而代之的是使用ArrayDataSource做爲tableView的datasource. 像下面這樣調用:網絡

void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
   cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
                                                cellIdentifier:PhotoCellIdentifier
                                            configureCellBlock:configureCell];
self.tableView.dataSource = photosArrayDataSource;
複製代碼

能夠看到咱們只須要簡單的把數據傳給photosArrayDataSource, 而且將其設置爲datasource, 系統就會在合適時機調用數據源方法, 提供數據.ide

可是上面的代碼還存在不足, 能夠看到咱們在block中設置了cell的內容, 這明顯是屬於View的邏輯, 不該該放入Controller中, 較好的方法是爲cell建立一個分類或者設置一個模型屬性. 後者較爲常見, 經過調用cell的set方法, 在set方法寫入設置cell內容的邏輯, 就能夠完成了.oop

第一種方法是爲cell建立一個分類.以下所示:佈局

#import "PlayerCell+ConfigureForVoice.h"

@implementation PlayerCell (ConfigureForVoice)

- (void)configureForVoice:(NSURL *)voiceUrl Width:(CGFloat)width IndexPath:(NSIndexPath *)indexPath{
    
    self.cellView.playUrl = voiceUrl;
    self.cellView.width = width;
    self.cellView.indexPath = indexPath;
    self.cellView.hidden = NO;
    
}
@end
複製代碼

這樣咱們就只須要調用cell的分類方法, 在分類中完成內容的設置, 就能夠把VIew的邏輯和Controller分開.url

void(^configureCellBlock)(PlayerCell*, NSURL*, NSIndexPath* ) = ^(PlayerCell *cell, NSURL *url, NSIndexPath *indexPath){

     [cell configureForVoice:url Width:self.tableView.width IndexPath:indexPath];

     cell.cellView.delegate = self;

   };
複製代碼

經過將DataSource的邏輯寫入單獨的類中, 咱們可讓代碼在任意ViewController中複用, 而且你還能加入一些額外的方法如tableView:commitEditingStyle:forRowAtIndexPath:, 除此以外你也能夠在單獨的類中寫入UICollectionViewDataSource, 使這個類同時支持兩種協議, 使代碼獲得更好的複用.spa

將業務邏輯移到 Model 中

下面是ViewController中一些代碼, 用來查找用戶目前的優先事項的列表.

- (void)loadPriorities {
    NSDate* now = [NSDate date];
    NSString* formatString = @"startDate = %@";
    NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
    NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate];
    self.priorities = [priorities allObjects];
}
複製代碼

其實這些邏輯應該是屬於Model層面上的, 咱們能夠爲Model建立一個分類, 在分類中實現查找邏輯.

這樣ViewController就只須要像下面這樣簡單的調用, 就能夠完成功能.

- (void)loadPriorities {
    self.priorities = [user currentPriorities];
}
複製代碼

User+Extensions.m 中:

- (NSArray*)currentPriorities {
    NSDate* now = [NSDate date];
    NSString* formatString = @"startDate = %@";
    NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
    return [[self.priorities filteredSetUsingPredicate:predicate] allObjects];
}
複製代碼

任何像上面這樣代碼和Model關係緊密而且代碼很輕鬆就能夠寫入Model中的狀況, 咱們均可以嘗試這麼作.

把網絡請求邏輯移到單獨的類

不要在ViewController中請求網絡的邏輯, 而是將請求網絡邏輯放入單獨的類中, 有不少優秀的第三方庫好比AFNetWorking和YTKNetWork, 咱們只須要簡單調用這些類提供的回調(多是以block形式或者delegate形式), 在成功回調和失敗回調中完成對應的邏輯處理. 這樣的好處是緩存和出錯控制都會在這個類中處理.

把 View 代碼移到 View 層

不要再ViewController中寫入複雜的View邏輯, 咱們只須要在控制器中簡單調用alloc init方法建立出View, 而後設置成控制器View的subView中. 而那些複雜的View佈局, 子View的添加則應直接寫到View中, 而後對外提供一些接口給控制器, 在合適的時間通知控制器, 控制器再去作對應的處理. 對於簡單的View咱們可使用xib的方式進行建立, 可是記住若View比較複雜最好不要採用這種方法, 否則後期維護更改工做量會特別大.

建立基類ViewController集成重複代碼

項目中在VIewController中咱們常常要寫一些重複的代碼, 好比設置導航條的標題內容及樣式, 嚮導航欄添加返回按鈕等等這些重複操做. 而後我就想到既然這些邏輯全部控制器基本都要實現, 那麼爲何不把代碼抽取到公共的基類, 而後建立的控制器都繼承這個公共基類, 這樣就可使得ViewController變得更加整潔.

以下面我就在基類中寫入了返回按鈕的代碼.

- (instancetype)init{
    if (self=[super init]) {
        [self setDefultBackBtn];
       
    }
    return self;
}
- (void)setDefultBackBtn {
    [self backItemWithImage:@"icon_back" highlight:@"icon_back" title:nil];
}
- (void)backItemWithImage:(NSString *)normalImageName
                         highlight:(NSString *)highlighImageName
                            title:(NSString *)title {
    UIButton * leftBtn=[UIButton buttonWithType:UIButtonTypeCustom];
    if (normalImageName) {
        [leftBtn setImage:[UIImage imageNamed:normalImageName] forState:UIControlStateNormal];
    }
    if (highlighImageName) {
        [leftBtn setImage:[UIImage imageNamed:highlighImageName] forState:UIControlStateHighlighted];
    }
    if (title) {
        leftBtn.titleLabel.font=[UIFont systemFontOfSize:18.0f];
        [leftBtn setTitle:title forState:UIControlStateNormal];
        [leftBtn setTitle:title forState:UIControlStateHighlighted];
        [leftBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [leftBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
    }else {
        leftBtn.titleLabel.font=[UIFont systemFontOfSize:18.0f];
        [leftBtn setTitle:@"    " forState:UIControlStateNormal];
        [leftBtn setTitle:@"    " forState:UIControlStateHighlighted];
        [leftBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [leftBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
    }
    
    
    [leftBtn addTarget:self action:@selector(backToLastVC) forControlEvents:UIControlEventTouchUpInside];
    [leftBtn sizeToFit];
    self.navigationItem.leftBarButtonItem=[[UIBarButtonItem alloc]initWithCustomView:leftBtn];
}

複製代碼

如上面代碼所示, 首先 用init方法時, 就將返回按鈕設置到導航欄上, 而且設置一些默認按鈕樣式以及點擊按鈕響應的方法, 這樣當咱們建立一個繼承基類控制器的子類控制器時就會默認設置好返回按鈕了. 大多數頁面都是具備返回按鈕的, 可是有時候按鈕有些特殊需求或者根本不須要返回按鈕. 那麼咱們只須要在基類中提供一些其餘接口就能夠適應這些需求. 好比下面:

//調用此方法不使用默認的返回按鈕
- (instancetype)initWithDefaultBackBtn:(BOOL)isNeed {
    if (self=[super init]) {
        if (isNeed){
        [self setDefultBackBtn];
        }
    }
    return self;
}
//而後調用此方法設置自定義的返回按鈕.
- (void)backItemWithImage:(NSString *)normalImageName
                         highlight:(NSString *)highlighImageName
                            title:(NSString *)title;
複製代碼

這裏只是簡單舉了一個返回按鈕的例子, 可是能夠作的遠不止於此. 任何ViewController中的公共邏輯, 咱們均可以寫入到類中.

通信

其餘常常在ViewController中發生的事情是與其餘ViewController, View, Model進行通信, 雖然這確實是ViewController應該作的事, 可是咱們儘可能但願用較少的代碼完成.

關於ViewController和Model通信已經有闡述的很好的技術來完成好比KVO, 可是ViewController之間的消息傳遞就顯的不是那麼清晰.

當一個ViewController想把一個狀態傳遞給另一個ViewController, 就會出現這樣不是很清晰的問題. 比較好的作法就是把狀態放入到一個單獨的對象裏, 而後把這個對象傳遞給其餘ViewController, 而後ViewController觀察並修改這個狀態. 這樣作的好處消息傳遞都在一個地方(被觀察的對象)進行, 咱們不用再糾結複雜的delegate回調.

總結

以上就是一些VIewController瘦身的技術. 他們的核心思想就是把不是ViewController的邏輯放到其餘地方, 將控制器的公共邏輯提取出來, 而且使代碼邏輯儘可能簡單. 咱們的最終目標:寫可維護的代碼. 只要把握這些原則, 咱們就可使得笨重的ViewController變得整潔.

參考文章: objccn.io/issue-1-1/

相關文章
相關標籤/搜索