鍍金池/ 教程/ iOS/ 更輕量的 View Controllers
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

更輕量的 View Controllers

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

你可以在 Github 上獲取關(guān)于這個(gè)問題的示例項(xiàng)目。

把 Data Source 和其他 Protocols 分離出來

UITableViewDataSource 的代碼提取出來放到一個(gè)單獨(dú)的類中,是為 view controller 瘦身的強(qiáng)大技術(shù)之一。當(dāng)你多做幾次,你就能總結(jié)出一些模式,并且創(chuàng)建出可復(fù)用的類。

舉個(gè)例,在示例項(xiàng)目中,有個(gè) PhotosViewController 類,它有以下幾個(gè)方法:

# 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;
}

這些代碼基本都是圍繞數(shù)組做一些事情,更針對(duì)地說,是圍繞 view controller 所管理的 photos 數(shù)組做一些事情。我們可以嘗試把數(shù)組相關(guān)的代碼移到單獨(dú)的類中。我們使用一個(gè) block 來設(shè)置 cell,也可以用 delegate 來做這件事,這取決于你的習(xí)慣。

@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

現(xiàn)在,你可以把 view controller 中的這 3 個(gè)方法去掉了,取而代之,你可以創(chuàng)建一個(gè) ArrayDataSource 類的實(shí)例作為 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;

現(xiàn)在你不用擔(dān)心把一個(gè) index path 映射到數(shù)組中的位置了,每次你想把這個(gè)數(shù)組顯示到一個(gè) table view 中時(shí),你都可以復(fù)用這些代碼。你也可以實(shí)現(xiàn)一些額外的方法,比如 tableView:commitEditingStyle:forRowAtIndexPath:,在 table view controllers 之間共享。

這樣的好處在于,你可以單獨(dú)測(cè)試這個(gè)類,再也不用寫第二遍。該原則同樣適用于數(shù)組之外的其他對(duì)象。

在今年我們做的一個(gè)應(yīng)用里面,我們大量使用了 Core Data。我們創(chuàng)建了相似的類,但和之前使用的數(shù)組不一樣,它用一個(gè) fetched results controller 來獲取數(shù)據(jù)。它實(shí)現(xiàn)了所有動(dòng)畫更新、處理 section headers、刪除操作等邏輯。你可以創(chuàng)建這個(gè)類的實(shí)例,然后賦予一個(gè) fetch request 和用來設(shè)置 cell 的 block,剩下的它都會(huì)處理,不用你操心了。

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

將業(yè)務(wù)邏輯移到 Model 中

下面是 view controller(來自其他項(xiàng)目)中的示例代碼,用來查找一個(gè)用戶的目前的優(yōu)先事項(xiàng)的列表:

- (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];
}

把這些代碼移動(dòng)到 User 類的 category 中會(huì)變得更加清晰,處理之后,在 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];
}

有些代碼不能被輕松地移動(dòng)到 model 對(duì)象中,但明顯和 model 代碼緊密聯(lián)系,對(duì)于這種情況,我們可以使用一個(gè) Store

創(chuàng)建 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 沒必要知道這些,所以我們創(chuàng)建了一個(gè) Store 對(duì)象來做這些事。通過分離,我們就可以復(fù)用這些代碼,單獨(dú)測(cè)試他們,并且讓 view controller 保持小巧。Store 對(duì)象會(huì)關(guān)心數(shù)據(jù)加載、緩存和設(shè)置數(shù)據(jù)棧。它也經(jīng)常被稱為服務(wù)層或者倉庫。

把網(wǎng)絡(luò)請(qǐng)求邏輯移到 Model 層

和上面的主題相似:不要在 view controller 中做網(wǎng)絡(luò)請(qǐng)求的邏輯。取而代之,你應(yīng)該將它們封裝到另一個(gè)類中。這樣,你的 view controller 就可以在之后通過使用帶有回調(diào)(比如一個(gè) completion 的 block)來請(qǐng)求網(wǎng)絡(luò)了。這樣的好處是,緩存和錯(cuò)誤控制也可以在這個(gè)類里面完成。

把 View 代碼移到 View 層

不應(yīng)該在 view controller 中構(gòu)建復(fù)雜的 view 層次結(jié)構(gòu)。你可以使用 Interface Builder 或者把 views 封裝到一個(gè) UIView 子類當(dāng)中。例如,如果你要?jiǎng)?chuàng)建一個(gè)選擇日期的控件,把它放到一個(gè)名為 DatePickerView 的類中會(huì)比把所有的事情都在 view controller 中做好好得多。再一次,這樣增加了可復(fù)用性并保持了簡單。

如果你喜歡 Interface Builder,你也可以在 Interface Builder 中做。有些人認(rèn)為 IB 只能和 view controllers 一起使用,但事實(shí)上你也可以加載單獨(dú)的 nib 文件到自定義的 view 中。在示例程序中,我們創(chuàng)建了一個(gè) PhotoCell.xib,包含了 photo cell 的布局:

http://wiki.jikexueyuan.com/project/objc/images/1-1.png" alt="" />

就像你看到的那樣,我們?cè)?view(我們沒有在這個(gè) nib 上使用 File's Owner 對(duì)象)上面創(chuàng)建了 properties,然后連接到指定的 subviews。這種技術(shù)同樣適用于其他自定義的 views。

通訊

其他在 view controllers 中經(jīng)常發(fā)生的事是與其他 view controllers,model,和 views 之間進(jìn)行通訊。這當(dāng)然是 controller 應(yīng)該做的,但我們還是希望以盡可能少的代碼來完成它。

關(guān)于 view controllers 和 model 對(duì)象之間的消息傳遞,已經(jīng)有很多闡述得很好的技術(shù)(比如 KVO 和 fetched results controllers)。但是 view controllers 之間的消息傳遞稍微就不是那么清晰了。

當(dāng)一個(gè) view controller 想把某個(gè)狀態(tài)傳遞給多個(gè)其他 view controllers 時(shí),就會(huì)出現(xiàn)這樣的問題。較好的做法是把狀態(tài)放到一個(gè)單獨(dú)的對(duì)象里,然后把這個(gè)對(duì)象傳遞給其它 view controllers,它們觀察和修改這個(gè)狀態(tài)。這樣的好處是消息傳遞都在一個(gè)地方(被觀察的對(duì)象)進(jìn)行,而且我們也不用糾結(jié)嵌套的 delegate 回調(diào)。這其實(shí)是一個(gè)復(fù)雜的主題,我們可能在未來用一個(gè)完整的話題來討論這個(gè)主題。

總結(jié)

我們已經(jīng)看到一些用來創(chuàng)建更小巧的 view controllers 的技術(shù)。我們并不是想把這些技術(shù)應(yīng)用到每一個(gè)可能的角落,只是我們有一個(gè)目標(biāo):寫可維護(hù)的代碼。知道這些模式后,我們就更有可能把那些笨重的 view controllers 變得更整潔。

擴(kuò)展閱讀