作者:Dywane,翻譯、修改自objc.io
連結:https://www.jianshu.com/p/cdf05c8dc3a5
原文連結:https://www.objc.io/issues/1-view-controllers/lighter-view-controllers/
導語
view controller通常是一個專案中最龐大的檔案,因為它裡面經常包含了不屬於它的程式碼,同時這也使它成為程式碼中最難以重用的部分。所以為view controller瘦身,讓其中的程式碼復用性更強,把相關程式碼放到正確的地方顯得尤其重要。
將Data Source和其他協議分離
為view controller瘦身最有效的方法就是把UITableViewDataSource中的程式碼移動到相關的類中,具體的方法可以參閱《iOS應用開發 簡明TableView》中的相關實現。
而更進一步,不只是TableView,這個方法可以擴充套件到其他的協議上,比如UICollectionViewDataSource。如果在開發中選擇使用UICollectionView代替UITableView時,這個方法可以讓你幾乎不用修改viewController中的任何東西,甚至可以讓Data Source同時支援兩個協議,給予了極大的便利性。
將弱業務邏輯移到Model中
註:markdown對程式碼塊的語法是開始和結束行都要新增:“`,其中 ` 為windows鍵盤左上角
首先是程式碼,以下的程式碼是幫助使用者查詢優先事項的串列:
-(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類中會讓它變得更加明晰,這時ViewController.m中會是:
-(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];
}
將這些程式碼移動的根本原因是因為ViewController.m是大部分業務邏輯的載體,本身程式碼的複雜度已經很高,所以這類跟業務關聯不大的程式碼比如日期轉換、影象裁剪、設定過濾器等的操作可以分離到各自的類中完成,一方面為viewController減負,另一方面也能增進程式碼的復用。
關於這個標題的翻譯我斟酌了比較久的時間,因為在原文中是“Move Domain Logic into the Model”,意為“把領域邏輯移到Model中”。對於“領域邏輯”一詞我進行過考究,大致意思為“穩定的、不會改變的邏輯關係”,同時在原文中也是使用了NSPredicate作為例子取用,而我認為其例子中的程式碼也是與業務相關的,只不過關聯性不大,而且不會輕易改動,所以使用了“弱業務邏輯”一詞代替了“領域邏輯”一詞。
把資料處理的邏輯移到服務層
一些程式碼可能沒辦法很有效的移動到model中,然而這些程式碼卻和model中的程式碼有清晰的關聯,對於這種問題,可以使用Store。比如在下麵的程式碼中,viewController需要完成從一個檔案中獲取一些資料,並對其進行操作:
-(void)readArchive
{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSURL *archiveURL = [bundle URLForResource:@"photodata" withExtension:@"bin"];
NSDate *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 object來完成。
透過對程式碼進行分離,能夠增進程式碼復用、對程式碼進行單元測試、保持view controller整潔等。同時能夠讓view controller更多關註於業務本身的內容,把資料的讀取 、快取、新建等操作交給服務層來處理。
把網路服務的邏輯移到Model層
這與上面提到的十分相似:不要把網路服務的邏輯放到view controller中,而應該把它們存放到不同的類中。
對於view controller,應該只是使用一個completion block來呼叫這些方法,而把網路請求、錯誤處理、快取處理交給這些類來完成
把處理view的程式碼移到view層
無論是使用Storyboard還是純程式碼編寫view,建立複雜的view的任務不應該交給view controller。
比如在需要建立一個日期選擇器時,更好的方法是把這些程式碼放到DatePickerView中,而不是放到ViewController中,和之前一樣,是為了復用和簡便。至於如何使用storyboard對view進行設定這裡不再贅述。
Communication(通訊)
view controller中最常發生的就是通訊,包括了和view層的通訊、和model層的通訊、和其他view controller的通訊等。儘管這是一個view controller必須做的事情,這裡依然有辦法能夠對程式碼進行縮減。
對於view controller和view、view controller和model之間的通訊已經存在大量的優秀經驗,比如使用KVO鍵值樣式傳值,或者使用Core Data中的NSFetchedResultsController等。然而,對於view controller之間的通訊的相關方法卻比較少。
比如在我現在做的專案中,一個view controller需要根據使用者身份的不同(家長/老師)來對view controller的 state 進行不同的設定,而這個 view controller 又在不同的 state 下與不同的 view controller 通訊,傳遞不同的值。在這種情況下 view controller 中的程式碼會變得相當臃腫和混亂,所以正確的做法是把這些不同的 state 放到不同的 object 裡面,再把它們傳給 view controller ,讓它們根據這個 state 來進行設定和修改,使我們不再需要被累贅的委託方法搞得十分混亂。
而在另一種情況下,各個 view controller 之間的跳轉邏輯十分複雜,存在著嚴重的橫向依賴,在這種情況下就不適宜使用普通的頁面跳轉樣式,而應該使用“中介者樣式”,建立一個coordinator controller,讓它來管理頁面跳轉的邏輯。
結論
事實上現在有各種各樣的方法為 view controller 減負,它們無一例外都是向著一個標的前進:寫出可維護的程式碼,只有把這些方法靈活運用,熟記於心,才能真正避免弄出笨重而又難以維護的 view controller。
想瞭解更多內容可以檢視我的主頁https://dywane.github.io/
朋友會在“發現-看一看”看到你“在看”的內容