View controllers 一般是 iOS 項目中最大的文件,而且它們包含了許多沒必要要的代碼。因此 View controllers 中的代碼幾乎老是複用率最低的。咱們將會看到給 view controllers 瘦身的技術,讓代碼變得能夠複用,以及把代碼移動到更合適的地方。html
你能夠在 Github 上獲取關於這個問題的示例項目。ios
把 UITableViewDataSource
的代碼提取出來放到一個單獨的類中,是爲 view controller 瘦身的強大技術之一。當你多作幾回,你就能總結出一些模式,而且建立出可複用的類。git
舉個例,在示例項目中,有個 PhotosViewController
類,它有如下幾個方法:github
# 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;
}
這些代碼基本都是圍繞數組作一些事情,更針對地說,是圍繞 view controller 所管理的 photos 數組作一些事情。咱們能夠嘗試把數組相關的代碼移到單獨的類中。咱們使用一個 block 來設置 cell,也能夠用 delegate 來作這件事,這取決於你的習慣。objective-c
@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
如今,你能夠把 view controller 中的這 3 個方法去掉了,取而代之,你能夠建立一個 ArrayDataSource
類的實例做爲 table view 的 data source。數組
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;
如今你不用擔憂把一個 index path 映射到數組中的位置了,每次你想把這個數組顯示到一個 table view 中時,你均可以複用這些代碼。你也能夠實現一些額外的方法,好比 tableView:commitEditingStyle:forRowAtIndexPath:
,在 table view controllers 之間共享。緩存
這樣的好處在於,你能夠單獨測試這個類,不再用寫第二遍。該原則一樣適用於數組以外的其餘對象。網絡
在今年咱們作的一個應用裏面,咱們大量使用了 Core Data。咱們建立了類似的類,但和以前使用的數組不同,它用一個 fetched results controller 來獲取數據。它實現了全部動畫更新、處理 section headers、刪除操做等邏輯。你能夠建立這個類的實例,而後賦予一個 fetch request 和用來設置 cell 的 block,剩下的它都會處理,不用你操心了。mvc
此外,這種方法也能夠擴展到其餘 protocols 上面。最明顯的一個就是 UICollectionViewDataSource
。這給了你極大的靈活性;若是,在開發的某個時候,你想用 UICollectionView
代替 UITableView
,你幾乎不須要對 view controller 做任何修改。你甚至可讓你的 data source 同時支持這兩個協議。app
下面是 view controller(來自其餘項目)中的示例代碼,用來查找一個用戶的目前的優先事項的列表:
- (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
:
在咱們初版的示例程序的中,有些代碼去加載文件並解析它。下面就是 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 對象會關心數據加載、緩存和設置數據棧。它也常常被稱爲服務層或者倉庫。
和上面的主題類似:不要在 view controller 中作網絡請求的邏輯。取而代之,你應該將它們封裝到另外一個類中。這樣,你的 view controller 就能夠在以後經過使用回調(好比一個 completion 的 block)來請求網絡了。這樣的好處是,緩存和錯誤控制也能夠在這個類裏面完成。
不該該在 view controller 中構建複雜的 view 層次結構。你可使用 Interface Builder 或者把 views 封裝到一個 UIView
子類當中。例如,若是你要建立一個選擇日期的控件,把它放到一個名爲 DatePickerView
的類中會比把全部的事情都在 view controller 中作好好得多。再一次,這樣增長了可複用性並保持了簡單。
若是你喜歡 Interface Builder,你也能夠在 Interface Builder 中作。有些人認爲 IB 只能和 view controllers 一塊兒使用,但事實上你也能夠加載單獨的 nib 文件到自定義的 view 中。在示例程序中,咱們建立了一個 PhotoCell.xib
,包含了 photo cell 的佈局:
就像你看到的那樣,咱們在 view(咱們沒有在這個 nib 上使用 File's Owner 對象)上面建立了 properties,而後鏈接到指定的 subviews。這種技術一樣適用於其餘自定義的 views。
其餘在 view controllers 中常常發生的事是與其餘 view controllers,model,和 views 之間進行通信。這固然是 controller 應該作的,但咱們仍是但願以儘量少的代碼來完成它。
關於 view controllers 和 model 對象之間的消息傳遞,已經有不少闡述得很好的技術(好比 KVO 和 fetched results controllers)。可是 view controllers 之間的消息傳遞稍微就不是那麼清晰了。
當一個 view controller 想把某個狀態傳遞給多個其餘 view controllers 時,就會出現這樣的問題。較好的作法是把狀態放到一個單獨的對象裏,而後把這個對象傳遞給其它 view controllers,它們觀察和修改這個狀態。這樣的好處是消息傳遞都在一個地方(被觀察的對象)進行,並且咱們也不用糾結嵌套的 delegate 回調。這實際上是一個複雜的主題,咱們可能在將來用一個完整的話題來討論這個主題。
咱們已經看到一些用來建立更小巧的 view controllers 的技術。咱們並非想把這些技術應用到每個可能的角落,只是咱們有一個目標:寫可維護的代碼。知道這些模式後,咱們就更有可能把那些笨重的 view controllers 變得更整潔。