歡迎光臨
每天分享高質量文章

iOS開發 簡化view controller

作者: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/

已同步到看一看
赞(0)

分享創造快樂