更輕量的 View Controllers

来源:http://www.cnblogs.com/jgCho/archive/2016/03/10/5263352.html
-Advertisement-
Play Games

iew controllers 通常是 iOS 項目中最大的文件,並且它們包含了許多不必要的代碼。所以 View controllers 中的代碼幾乎總是復用率最低的。我們將會看到給 view controllers 瘦身的技術,讓代碼變得可以復用,以及把代碼移動到更合適的地方。 你可以在 Gith


iew controllers 通常是 iOS 項目中最大的文件,並且它們包含了許多不必要的代碼。所以 View controllers 中的代碼幾乎總是復用率最低的。我們將會看到給 view controllers 瘦身的技術,讓代碼變得可以復用,以及把代碼移動到更合適的地方。

你可以在 Github 上獲取關於這個問題的示例項目

把 Data Source 和其他 Protocols 分離出來

UITableViewDataSource 的代碼提取出來放到一個單獨的類中,是為 view controller 瘦身的強大技術之一。當你多做幾次,你就能總結出一些模式,並且創建出可復用的類。

舉個例,在示例項目中,有個 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;
}

這些代碼基本都是圍繞數組做一些事情,更針對地說,是圍繞 view controller 所管理的 photos 數組做一些事情。我們可以嘗試把數組相關的代碼移到單獨的類中。我們使用一個 block 來設置 cell,也可以用 delegate 來做這件事,這取決於你的習慣。

@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,剩下的它都會處理,不用你操心了。

此外,這種方法也可以擴展到其他 protocols 上面。最明顯的一個就是 UICollectionViewDataSource。這給了你極大的靈活性;如果,在開發的某個時候,你想用 UICollectionView 代替 UITableView,你幾乎不需要對 view controller 作任何修改。你甚至可以讓你的 data source 同時支持這兩個協議。

將業務邏輯移到 Model 中

下麵是 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

創建 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 對象會關心數據載入、緩存和設置數據棧。它也經常被稱為服務層或者倉庫

把網路請求邏輯移到 Model 層

和上面的主題相似:不要在 view controller 中做網路請求的邏輯。取而代之,你應該將它們封裝到另一個類中。這樣,你的 view controller 就可以在之後通過使用回調(比如一個 completion 的 block)來請求網路了。這樣的好處是,緩存和錯誤控制也可以在這個類裡面完成。

把 View 代碼移到 View 層

不應該在 view controller 中構建複雜的 view 層次結構。你可以使用 Interface Builder 或者把 views 封裝到一個 UIView 子類當中。例如,如果你要創建一個選擇日期的控制項,把它放到一個名為 DatePickerView 的類中會比把所有的事情都在 view controller 中做好好得多。再一次,這樣增加了可復用性並保持了簡單。

如果你喜歡 Interface Builder,你也可以在 Interface Builder 中做。有些人認為 IB 只能和 view controllers 一起使用,但事實上你也可以載入單獨的 nib 文件到自定義的 view 中。在示常式序中,我們創建了一個 PhotoCell.xib,包含了 photo cell 的佈局:

PhotoCell.xib screenshot

就像你看到的那樣,我們在 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 變得更整潔。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • jQuery Easy是一組基於jQuery的UI插件集合,EasyUI的目標就是幫助web開發者更輕鬆的打造出功能豐富並且美觀的UI界面。開發者不需要編寫複雜的javascript,也不需要對css樣式有深入的瞭解,開發者需要瞭解的只有一些簡單的html標簽。 註:jQuery的特點 基於jQue
  • 在ECMAScript5中對Object新增的些方法,學習以及demo
  • HTML代碼: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://
  • HTML代碼: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://
  • 一、框架介紹 RequireJS 資料:http://www.requirejs.cn/RequireJS的目標是鼓勵代碼的模塊化,它使用了不同於傳統<script>標簽的腳本載入步驟。可以用它來加速 、優化代碼,但其主要目的還是為了代碼的模塊化。它鼓勵在使用腳本時以module ID替代URL地址
  • 依賴註入起源於實現控制反轉的典型框架Spring框架,用來削減電腦程式的耦合問題。簡單來說,在定義方法的時候,方法所依賴的對象就被隱性的註入到該方法中,在方法中可以直接使用,而不需要在執行該函數的時候再參數中添加這些依賴對象。 理解很簡單,我們以一個例子說明 var $name = "chua",
  • 其實要講的就只有一個,那就是給瀏覽器加上本地Ajax運行,當你使用到了某些angularJS的功能的時候,例如路由,你直接運行頁面打開之後你會發現是空白的,打開控制台 發現 XMLHttpRequest cannot load 這種字眼的時候,其實是告訴你瀏覽器禁止了本地的Ajax申請,你需要給瀏覽
  • 削減是一個從源代碼中刪除不必要的字元的技術使它看起來簡單而整潔。這種技術也被稱為代碼壓縮和最小化。在這裡,我們為你收集了10個最好的JavaScript壓縮工具將幫助您刪除不必要的空格,換行符,評論,等等。這樣,你將使你的源代碼,無雜波。削減你的源代碼將改善載入時間和Web應用的性能。 這裡是你的完
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...