【iOS開發】---- ARC 入門
用他們自己的話說,MusicBrainz 是”一個開放的音樂百科全書, 為公眾提供音樂的元資料”。 他們提供一個免費的 XML web service 來供你的應用呼叫。 如果像更多的瞭解 MusicBrainz, 可以去他們的網站看看 http://musicbrainz.org.
下載這個教程的起始專案, 並用 Xcode 開啟它。 這個專案包含下面的原始檔:
- AppDelegate.h/.m: 應用的代理。 這個沒什麼特別的,每個應用都有它。 它載入控制器,然後將它放到 window 上面。
-
MainViewController.h/.m/.xib: 應用的控制器。 它有一個 table
view 和一個 search bar, 並且做了最多的工作。
- SoundEffect.h/.m: 一個簡單的類,用於播放特效聲音。 當 MusicBrainz 搜尋完成時,應用會發出一個小小的蜂鳴聲。
- main.m: 應用的入口。
另外, 這個應用用到了兩個第三方庫。 你的應用可能會用到一些外部元件, 這也正好可以學習如何用 ARC 來處理這些庫。
-
AFHTTPRequestOperation.h/.m: 是 AFNetworking 庫的一部分,可以讓你簡單的請求
web service。 我沒有匯入這個庫的全部, 因為我們只需要用到這一個類。 你可以在這裡找到完整的包 https://github.com/gowalla/AFNetworking
- SVProgresHUD.h/.m/.bundle: 當進行搜尋的時候,它會在螢幕上顯示一個進度指示器。 你以前可能還沒見過 .bundle 檔案。 這是一個特殊的目錄,它包含了 SVProgressHUD 需要用到的圖片。 可以在 Finder 中通過右鍵點選 .bundle 檔案,然後選擇 “顯示包內容” 來檢視這些圖片。 關於這個元件更多的資訊,可以檢視這裡: https://github.com/samvermette/SVProgressHUD
讓我們快速的瀏覽一遍控制器的程式碼, 這樣你能全面的瞭解應用是如何工作的。 MainViewController 是 UIViewController 的子類。 它的 nib 檔案包含了一個 UITableView 物件和一個 UISearchBar 物件:
圖9
Table View 顯示了 searchResults 陣列中的內容。這個指標初始值是 nil。 當用戶進行了一次搜尋, 我們用 MusicBrainz 伺服器響應的資料來填充這個陣列。 如果沒有相關的搜尋結果,這個陣列將會是空的(但不是 nil), Table 上面會顯示 “(Nothing found)”。 這些功能都是通過 UITableViewDataSource 的方法完成的:numberOfRowsInSection 和 cellForRowAtIndexPath。
實際的搜尋過程是在 searchBarSearchButtonClicked 中進行的, 它是 UISearchBarDelegate 協議的一部分。
- (void)searchBarSearchButtonClicked:(UISearchBar *)theSearchBar
{
[SVProgressHUD showInView:self.view status:nil
networkIndicator:YES posY:-1
maskType:SVProgressHUDMaskTypeGradient];
首先,我們建立了一個新的 HUD, 並且將他顯示在 TableView 和 Search bar 的上面, 在網路請求完成之前, 阻止使用者的任何輸入:
然後我們建立 HTTP 請求的 URL。 我們用 MusicBrainz 的 API 來搜尋藝術家。
NSString *urlString = [NSString stringWithFormat:
@"http://musicbrainz.org/ws/2/artist?query=artist:%@&limit=20",
[self escape:searchBar.text]];
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:[NSURL URLWithString:urlString]];
要搜尋的文字通過 escape 方法進行 URL 編碼: 保證我們的 URL 是有效的。 空格和其他特殊符號會轉換成類似這樣的形式: %20 。
NSDictionary *headers = [NSDictionary dictionaryWithObject:
[self userAgent] forKey:@"User-Agent"];
[request setAllHTTPHeaderFields:headers];
我們為 HTTP 請求添加了一個自定義的 User-Agent 頭。 MusicBrainz API 需要它。 所有的請求都應該 “有一個合適的 User-Agent 請求頭,以便用來標識傳送請求的應用和版本。” 和你正在用的 API 配合好總是一個好主意, 所以我們像這樣構建了一個 User-Agent 請求頭:
com.yourcompany.Artists/1.0 (unknown, iPhone OS 5.0,
iPhone Simulator, Scale/1.000000)
(I took this formula from another part of the AFNetworking library and put it into the userAgent method in the view controller.)
MusicBrainz API 還有一些其他的規則。 客戶端應用每秒只能傳送一個請求到 web service 中, 否則它們的 IP 有可能被遮蔽。 這對我們的應用來說不是個大問題 — 使用者不太可能做這麼多次的搜尋 — 所以我們不需要預防這類操作。
當我們建立完 NSMutableURLRequest 物件, 我們把它發給 AFHTTPRequestOperation 來處理:
AFHTTPRequestOperation *operation = [AFHTTPRequestOperation
operationWithRequest:request completion:^(NSURLRequest *request,
NSHTTPURLResponse *response, NSData *data, NSError *error)
{
// ...
}];
[queue addOperation:operation];
AFHTTPRequestOperation 是 NSOperation 的一個子類, 這也意味著我們可以把它新增到 NSOperationQueue(在 queue 變數上) 中, 並且它會進行非同步處理。 因為用到了 HUD, 當正在請求資料的時候,應用忽略了所有的使用者輸入。
我們給 AFHTTPRequestOperation 指定了一個 block, 當請求完成的時候它會被呼叫。 在 block 中,我們首先檢測請求是否成功(通過 HTTP 狀態碼 200)。 在這個應用中我們對為什麼失敗不感興趣; 如果失敗了,我們僅僅讓 HUD 以一個”失敗”動畫消失。 注意 completion block 不一定會在主執行緒上執行, 所以我們需要將對 SVProgressHUD 的呼叫包裝到 dispatch_async() 中。
if (response.statusCode == 200 && data != nil)
{
. . .
}
else // something went wrong
{
dispatch_async(dispatch_get_main_queue(), ^
{
[SVProgressHUD dismissWithError:@"Error"];
});
}
現在開看看有趣的部分。 如果請求成功了, 我們建立 searchResults 陣列,並且解析響應。 服務端返回的資料是 XML 格式的,所以我們用 NSXMLParser 來解析它。
self.searchResults = [NSMutableArray arrayWithCapacity:10];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];
[parser release];
[self.searchResults sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
你可以在 NSXMLParserDelegate 的方法中找到解析 XML 的邏輯程式碼, 我們實際上僅僅查找了名稱為 “sort-name” 的元素。 它們包含了藝術家的姓名。 我們將這些姓名以 NSString 物件的形式放入 searchResults 陣列。 當 XML 解析完成時, 我們以字母表順序對這個陣列進行排序, 並且在主執行緒中更新介面顯示:
dispatch_async(dispatch_get_main_queue(), ^
{
[self.soundEffect play];
[self.tableView reloadData];
[SVProgressHUD dismiss];
});
這些就是這個應用如何工作的。 它使用手工記憶體管理,並且沒有用到任何 iOS 5 的特性。 現在,讓我們把它轉換到 ARC 吧。
自動轉換
我們將要把 Artists 應用轉換到 ARC。 簡單來說, 我們不再需要呼叫 retain, release 和 autorelease 了, 但在一些特定情況下,我們還需要做一些特殊處理。
這裡有三個方式能讓你的應用做到 ARC 相容:
- Xcode 有一個自動轉換工具, 他可以遷移你的原始碼。
- 你可以手工的轉換這些檔案。
- 你可以對那些你不想轉換的檔案禁用 ARC。 這對那些你不想混在一起的第三方庫非常有用。
我們將在 Artists 應用中用到所有這些操作, 僅為了向你展示所有這些是如何工作的。 在這個部分, 我們將會通過 Xcode 的自動轉換工具來轉換原始檔, 除了 MainViewController 和 AFHTTPRequestOperation。
在我們做這些事情之前, 你應該把專案拷貝一份,因為這個工具會覆蓋原來的檔案。 Xcode 會提供一個對原始碼的預覽介面, 但是為了防止丟失,我無論如何也會備份一份。
ARC 是 LLVM 3.0 的一個新特性。 你現有的專案很可能用的是老的 GCC 4.2 或者 LLVM-GCC 編譯器, 所以首先要做的是將專案的編譯器切換到新版本,看一看編譯器是否在非 ARC 模式。 進入 Project Settings 介面,
選擇 Artists target, 在 Build Settings 選項卡中的搜尋框中輸入 “compiler”. 這樣可以過濾列表,展示出編譯選項:
圖11
點選 Compiler for C/C++/Objective-C 選項,修改它為 Apple LLVM compiler 3.0:
在 Warnings 頭中,還要把 Other Linker Flags 設定為 -Wall。 這樣編譯器將會檢測所有會導致問題的情況。 預設情況下,這些警告訊息是被關閉的,但是我發現總是把他們開啟並且將每一個都看作是致命錯誤是很有用的。 換句話說, 如果編譯器給出任何警告,我將會在繼續其他工作之前修復它。 是否在你自己的專案中也這樣做完全取決於你, 但是在轉換到 ARC 的過程中, 我推薦你仔細看看編譯器給出的每一個問題。
同樣地, 也要在 Build Options 頭中開啟 Run Static Analyzer:
Xcode 現在將會在每次構建專案的時候執行靜態分析。 這會讓構建的速度稍微慢一點, 但是作為我們這種規模的應用來說,這不算什麼。
讓我們來構建一下應用, 看看新的編譯器會給出什麼問題. 首先我們用 Product -> Clean(或 Shift-Cmd-K) 進行一次清理。 然後按下 Cmd-B 來構建應用。 Xcode 應該不會收到任何警告。 如果你在將你自己的專案轉換到 ARC, 並且收到了警告訊息, 那麼現在正是修復他們的時候。
讓我們把編譯器切換到 ARC 模式,並且再次構建應用。 我們收到了一大堆錯誤訊息, 現在正好可以看看這些究竟是什麼。
仍然在 Build Settings 螢幕中, 切換到 “All” 可以看到所有可用的設定(Basic選項僅僅顯示最常用到的設定)。 搜尋 “automatic”, 設定 Objective-C Automatic Reference Counting 選項為 YES。 設定一個專案範圍的標記, 用來告訴 Xcode 你將要用 ARC 編譯器來編譯你的專案中所有的原始檔。
再次構建應用, 你應該會看到一大堆錯誤:很明顯,我們要進行一些遷移! 大多數的錯誤都很明顯,他們說的都是你不能再用 retain,release 和 autorelease 了。 我們可以完全手工的修正這些錯誤, 但是使用自動轉換工具會更容易一些。 這個工具會用 ARC 模式來編譯應用, 然後對每一個它遇到的錯誤的地方進行重寫,直到專案不再報錯。
在 Xcode 選單中, 選擇 EditRefactorConvert to Objective-C ARC.。
一個新的視窗會彈出來, 讓你選擇哪些部分是你想要轉換的:
我們不希望轉換整個專案, 只選擇下面這些檔案:
- main.m
- AppDelegate.m
- SVProgressHUD.m
- SoundEffect.m
這個對話方塊顯示了一個小警告圖示,用來指示這個專案已經使用了 ARC。 這是因為我們之前在 Build Settings 中開啟了 Objective-C Automatic Reference Counting 選項, 所以自動轉換工具會認為它已經是一個 ARC 專案了。 你可以忽略這個警告, 他不會對轉換造成影響。
按下 Precheck 按鈕來開始轉換。 這個工具首先會檢測你的程式碼是否處於足夠好的狀態來轉換到 ARC。 我們之前用新的 LLVM 3.0 編譯器成功的構建了我們的應用, 但是很明顯這次不行。 Xcode 會給出如下錯誤:
它提示 “ARC readiness issues”, 我們應該開啟 “Continue building after errors” 選項。 我們應該先開啟這個選項。 開啟 Xcode 的 Preferences 視窗 (在 Xcode 的選單中), 進入 General 標籤。 開啟選項 “Continue building after errors”: