ViewControllers 瘦身計劃 (一)

ppsheep發表於2016-12-06

View controllers 通常是 iOS 專案中最大的檔案,並且它們包含了許多不必要的程式碼。所以 View controllers 中的程式碼幾乎總是複用率最低的。接下來我將結合一些我自己看到的東西和平時在使用的方法,來節省ViewController中的程式碼量。

歡迎大家關注我的公眾號,我會定期分享一些我在專案中遇到問題的解決辦法和一些iOS實用的技巧,現階段主要是整理出一些基礎的知識記錄下來



文章也會同步更新到我的部落格:
ppsheep.com

把 Data Source 和其他 Protocols 分離出來

我們在平時的編碼中,最經常使用到的一個控制元件就是UITableView了,那我們每次需要使用到tableview的時候,都需要寫一些重複的程式碼,比如

#pragma mark - tableview datasource

#pragma mark - tableview datasource

- (PPSFriend *)friendAtIndexPath:(NSIndexPath *)indexPath{
    return self.friends[indexPath.row];
}

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

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    PPSFriendCell *cell = [tableView dequeueReusableCellWithIdentifier:@"friendCell" forIndexPath:indexPath];
    PPSFriend *friend = [self friendAtIndexPath:indexPath];
    cell.textLabel.text = friend.name;
    return cell;
}複製程式碼

像上面一些程式碼,我們每天都在寫,每次用到UITableView都需要重寫這些毫無技術可言的程式碼,那我們試想一下能否寫一個封裝類,將這些重複的方法全部封起來,多次使用呢?答案當然是,可以的。

上面的程式碼,其實都是在圍繞著friends這個陣列做一系列的事情,我們可以獨立出來一個類,使用一個block或者delegate來設定cell,當然這取決於你的習慣。

#pragma mark - tableview config cell
- (void)configCell{
    void (^configCell)(PPSFriendCell *, PPSFriend *) = ^(PPSFriendCell *cell, PPSFriend *friend){
        cell.textLabel.text = friend.name;
    };
    PPSArrayDatasource *datasources = [[PPSArrayDatasource alloc] initWithItems:self.friends cellIdentifier:@"friendCell" configureCellBlock:configCell];
    self.tableView.dataSource = datasources;
}複製程式碼

現在,你可以把 view controller 中的這 3 個方法去掉了,取而代之,你可以建立一個 PPSArrayDatasource 類的例項作為 table view 的 data source。

現在你不用擔心把一個 index path 對映到陣列中的位置了,每次你想把這個陣列顯示到一個 table view 中時,你都可以複用這些程式碼。你也可以實現一些額外的方法,比如

tableView:commitEditingStyle:forRowAtIndexPath:複製程式碼

多個section

還有一種情況,如果是多個section的情況下,我們還可以再擴充套件一下,將block定義為

typedef void(^TableViewCellConfigureBlock)(id cell, id item, NSIndexPath *indexPath);複製程式碼
#pragma mark - tableview config cell
- (void)configCell{
    void (^configCell)(PPSFriendCell *, PPSFriend *, NSIndexPath *) = ^(PPSFriendCell *cell, PPSFriend *friend, NSIndexPath *indexPath){
        cell.textLabel.text = friend.name;
    };
    PPSArrayDatasource *datasources = [[PPSArrayDatasource alloc] initWithItems:self.friends cellIdentifier:@"friendCell" configureCellBlock:configCell];
    self.tableView.dataSource = datasources;
}複製程式碼

那麼在PPSArrayDatasource的items中裝的應該就是一個一個的陣列了,分別對應的每個section,這裡我只是針對這種情況說明一下,不管是多個section還是單個section都可以使用這種方法,來瘦身

在 table view controllers 之間共享。

這樣的好處在於,你可以單獨測試這個類,再也不用寫第二遍。該原則同樣適用於陣列之外的其他物件。

將業務邏輯移到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];
}複製程式碼

把這些程式碼移動到 User 類的 category 中會變得更加清晰,處理之後,在 View Controller.m 中看起來就是這樣:

- (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 程式碼緊密聯絡,對於這種情況,我們可以使用一個 Store:

建立store類

在一些情況下中,我們需要載入檔案並解析它。下面就是 view controller 中的程式碼:

- (void)readArchive {
    NSBundle* bundle = [NSBundle bundleForClass:[self class]];
    NSURL *archiveURL = [bundle URLForResource:@"photodata"
                                 withExtension:@"bin"];
    NSAssert(archiveURL != nil, @"Unable to find archive in bundle.");
    NSData *data = [NSData dataWithContentsOfURL:archiveURL
                                         options:0
                                           error:NULL];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    _users = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"users"];
    _photos = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"photos"];
    [unarchiver finishDecoding];
}複製程式碼

但是 view controller 沒必要知道這些,所以我們可以建立了一個 Store 物件來做這些事。通過分離,我們就可以複用這些程式碼,單獨測試他們,並且讓 view controller 保持小巧。Store 物件會關心資料載入、快取和設定資料棧。它也經常被稱為服務層或者倉庫。

把網路層請求邏輯移到model層

和上面的主題相似:不要在 view controller 中做網路請求的邏輯。取而代之,你應該將它們封裝到另一個類中。這樣,你的 view controller 就可以在之後通過使用回撥(比如一個 completion 的 block)來請求網路了。這樣的好處是,快取和錯誤控制也可以在這個類裡面完成。

把View移到View層

不應該在 view controller 中構建複雜的 view 層次結構。你可以使用 Interface Builder 或者把 views 封裝到一個 UIView 子類當中。例如,如果你要建立一個選擇日期的控制元件,把它放到一個名為 DatePickerView 的類中會比把所有的事情都在 view controller 中做好好得多。再一次,這樣增加了可複用性並保持了簡單。

簡單來說,就是將一個viewcontroller中複雜的view構造,放到一個單獨的view類中,然在viewcontroller中,只需要構建一個這個類就行。

總結

我們已經看到一些用來建立更小巧的 view controllers 的技術。我們並不是想把這些技術應用到每一個可能的角落,只是我們有一個目標:寫可維護的程式碼。知道這些模式後,我們就更有可能把那些笨重的 view controllers 變得更整潔。

相關文章