前言
總的來說試圖控制器是程式碼最多的檔案並且大多都是不可複用的程式碼。下面將教你做到檔案瘦身、程式碼複用、合理程式碼分配。
分離資料來源與協議類
一句話就是將UITableViewDataSource
部分的程式碼單獨寫成一個類,通過繼承它達到重用。
栗子,這裡有一個PhotosViewController
類遵循如下的方法:
# pragma mark Pragma
- (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;
}
上述程式碼有陣列大量的處理。我們將陣列操作相關得程式碼搬到我們自己建立的類。我們根據業務需求以及“癖好”使用block來配置cell:
@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
可以用專門定製類生成的例項去執行上述三個方法所要執行的任務。
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;
同樣可以在這個剝離出的類中實現tableView:commitEditingStyle:forRowAtIndexPath:
諸如此類的方法。
這樣做的好處分離出來的程式碼我們可以進行獨立測試。
在使用CoreData的時候不是使用Array作為資料來源支撐,而是依賴一個資料抓取的控制器。它實現了所有重新整理,分段,刪除的功能。你可以建立一個例項實現抓取資料請求邏輯以及配置cell邏輯。
更進一步的說,這種分離代理成抽象類的方法可以做的更加通用,比如同時使用表格檢視與集合檢視這樣的好處是增加了程式碼的可重用度。
將邏輯程式碼移到資料層
如下的程式碼是在控制器中獲取啟用優先順序的使用者:
- (void)loadPriorities {
NSDate* now = [NSDate date];
NSString* formatString = @"startDate <= %@ AND endDate >= %@";
NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate];
self.priorities = [priorities allObjects];
}
為了讓邏輯更加清晰簡潔,我們將這些程式碼移到使用者類,控制器中程式碼將得到簡化:
- (void)loadPriorities {
self.priorities = [self.user currentPriorities];
}
被搬走的程式碼在User+Extensions.m:
- (NSArray*)currentPriorities {
NSDate* now = [NSDate date];
NSString* formatString = @"startDate <= %@ AND endDate >= %@";
NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
return [[self.priorities filteredSetUsingPredicate:predicate] allObjects];
}
當邏輯程式碼不是移入一個資料模型可以解決的時候,我們可以把它丟到資料倉儲裡面:
建立資料倉儲類
當我們要從一個檔案獲取資料並載入它們的時候,我們的檢視控制器:
- (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];
}
我們知道檢視控制器肯定不是幹這個的。我們建立一個倉庫來做這些活兒。將這些程式碼分離出去,便於重用,測試主要能保證我們的控制器乾淨。這個資料倉儲作用是資料載入,獲取還有建立資料層。資料倉儲還可以叫做服務層以及倉儲層。
將頁面服務邏輯移到資料層
處理方法相同,分離一個抽象類進行單獨處理。回撥處理要做完整,包括資料抓取以及錯誤處理。
將檢視程式碼移動帶檢視層
複雜層級的用XIB或者SB(其實都該這麼搞)
通訊
通訊是控制器的主要任務,這部分的程式碼我們也需要儘可能的精簡。
控制器與資料物件的通訊自然不用說了,KVO等模式分分鐘搞定。但是控制器之間的通訊往往不那麼方便。
上述的問題通常出現於我們的控制器有一些狀態量並且與多個檢視控制器進行通訊的時候。。通常,將這些狀態放到一個獨立的物件,並將這個物件傳給檢視控制器通過這樣可以來觀察這個狀態。這樣做的好處是程式碼可以集中管理(不用在內嵌代理)。這是個複雜的命題,後續做完整的處理。
總結
編寫高度解耦、簡潔、可維護的檢視控制器!