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 變得更整潔。