打造轻量化的View Controller
2019獨角獸企業重金招聘Python工程師標準>>>
本文由破船譯自objc
?
小引
很早以前就看到了這篇文章,該文是Lighter View Controllers中比較重要的一篇,來自http://www.objc.io/,該站點的目標是致力于介紹Objective-C中最佳的實踐技能和高級技術,以期刊的形式發表,每期一個主題,第一期就是以Lighter View Controllers為主。共有5篇文章,由于老破船精力有限,可能不會全部翻譯,感興趣的讀者可以前往官方站點,進行閱讀,千萬別苦等破船進港時,萬一等到花謝草枯了,可不好喲。
?
本文目錄如下:
簡介
剝離Data Source和其它Protocols
將業務邏輯移至Model Layer
創建Store類
將Web Service邏輯移至Model Layer
將View代碼移至View Layer
與別的對象進行通訊
小結
延伸閱讀
?
簡介
在iOS工程中,view controllers經常是最大的文件,引起這樣的主要原因是開發者在view controllers中編寫了大量非必須代碼,實際上,view controllers可以重用其中的許多代碼。下面我們就來看看,有什么好的辦法可以對view controllers進行瘦身,加強代碼的可重用性(reusable),并將代碼放到適當的地方。
提醒:本文涉及到的示例工程已經放到GitHub上了。
?
剝離Data Source和其它Protocols
對view controllers瘦身最佳的方法之一就是將UITableViewDataSource涉及到的代碼從view controllers中抽取出來,并封裝到自己的一個類中。如果不止在一個view controllers中使用到UITableViewDataSource,那么會提高封裝出來這個類的可重用性。
?
譯者注:此處有一個前提條件原作者沒有說明,我們需要注意一下——這里的view controllers是使用到了UITableView。
?
下面我們來看一個例子,在上面給出的示例工程中,有一個類PhotosViewController,里面原本有如下3個方法:
#?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;?}?
上面3個方法中都涉及到了數組,并且在最后一個方法中利用索引(indexPatha)給UITableViewCell指定了相應的圖片(view controllers管理著這些圖片資源)。下面我們就來試著把與數組相關的代碼封裝到我們自己的一個類中。
?
如下代碼所示,這里通過一個block來配置UITableViewCell,當然,也可以使用delegate對UITableViewCell進行配置,這主要取決于開發者。
@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 controllers中的那3個方法移除掉,并創建自定義類的一個示例對象,然后將其設置為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;?
現在,當每次希望將數組中的內容顯示到table view中時,不必再考慮如何將index path映射到數組中的準確位置了,只需要重新上面的代碼即可。另外,我們也可以在自定義類中,實現另外的一些方法(例如tableView:commitEditingStyle:forRowAtIndexPath:),以共享給所有的table view controllers。
?
這也會帶來一個好處:可以單獨的對自定義的這個類進行測試,而不用擔心要重新寫一些測試代碼。其實,如果我們寫的代碼跟這里的情況類似,那么也可以這樣做。
?
今年我在工作中寫的一個應用程序,大量使用了Core Data。我也創建了類似的類。該類實現了動畫更新的所有邏輯,section header處理,以及相關刪除操作等。你可以創建該類的一個示例,然后設置一下對其調用的方法,以及配置cell的一個block,剩下的任務就能自動處理了。
?
此外,上面介紹的這種方法可以延伸到別的protocols,這能夠給程序開發中帶來很大的靈活性。例如UICollectionViewDataSource,在開發過程中,如果希望用UICollectionView替換已有的UITableView,我們幾乎不需要對view controllers做很大的改動,甚至還能使我們的data source同時支持兩種protocols(UITableViewDataSource和UICollectionViewDataSource)。
?
將業務邏輯移至Model Layer
下面的示例代碼(另外一個工程)位于view controller,作用是找出針對用戶active priority的一個列表。
-?(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];?}?
實際上,如果把這個方法移至User類的一個category中,會讓代碼更加清晰。此時,在View Controller.m文件中看起來應該是這樣的:
-?(void)loadPriorities?{?self.priorities?=?[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];?}?
實際開發中,有一些代碼很難將其移至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中剝離出來,不僅可以對其重用和單獨測試,另外還能對view controller瘦身。Store類專注于數據的加載、緩存,以及對數據庫進行配置。這里的Store也經常叫做service layer或者repository。
?
將Web Service邏輯移至Model Layer
這里的方法實際上跟上面介紹的非常類似:不要在view controller中做web service邏輯處理,而是將相關的邏輯處理封裝到不同的類中。然后我們的view controller通過callback handler(例如一個completion block)來調用這些類的方法。
?
這樣做帶來的一個好處就是我們可以方便在這些封裝類中做緩存和錯誤處理。
?
將View代碼移至View Layer
切記不要在view controller中構建復雜的view結構。可選方案是:要么利用interface builder,或者就是將view的構建封裝到一個UIView子類中。例如,如果你要構建一個date picker控件,可以基于DatePickerView類來構建,而不要把所有構建邏輯都放入view controller中。這樣不僅能增加控件的可重用性,還能讓代碼簡單化。
?
如果你喜歡用interface builder,那么也同樣可以在interface builder中做這些事情。我們的一些開發者可能認為只有view controller才能在interface builder中使用,其實我們可以通過加載一個單獨的nib文件,來加載我們在nib文件中定制的view。在本文給出的示例工程中,我創建了一個PhotoCell.xib文件,該文件中定制了一個photo cell:
?
如上圖所示,我在這個view中創建了兩個屬性:photoDateLable和photoTitleLable(注意:在這里的xib文件中沒有使用File’s Owner object),并將這兩個屬性連接到制定的subview中。
上面介紹的技巧同樣可以方便的用于別的一些custom view。
?
與別的對象進行通訊
在view controller中與別的view controller、model和view通訊是非常頻繁的。雖然這確實是由controller負責的,不過,我們還是希望用最少的代碼來完成相關的事情。
?
目前已經有許多技術可以用于view controller和model對象之間的通訊(例如KVO),不過view controller之間通訊的技術貌似不太明朗。
?
我們可能會經常遇到這樣的問題:一個view controller有許多狀態需要與其它多個view controller進行勾兌。通常,需要把這些狀態封裝到一個單獨的對象中,然后將其傳送到對應的view controller中,在這些view controller中對這些狀態進行觀察并修改即可——這樣帶來的優點是所有的狀態都在一個地方,開發者不用糾結于delegate的callback。
?
實際上,關于對象間的通訊是一個復雜的topic,在今后的文章中,我們可能會對其進行深度分析。
?
小結
上面我們學習了一些對view controller瘦身的技巧。這些技巧并不強求用與所有的地方,其實只有一個目標:編寫可維護的代碼。大家通過了解這些方法,可以知道有更多的方法來應對復雜的view controller,讓其看起來更加清晰。
?
延伸閱讀
View Controller Programming Guide for iOS
Cocoa Core Competencies: Controller Object
Writing high quality view controllers
Stack Overflow: Model View Controller Store
Unburdened View Controllers
Stack Overflow: How to avoid big and clumsy UITableViewControllers on iOS
轉載于:https://my.oschina.net/w11h22j33/blog/206575
總結
以上是生活随笔為你收集整理的打造轻量化的View Controller的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JQ之赋值方法,new的区别
- 下一篇: “代理”那点事儿-使用代理和搭建简单代理