iOS 5 ARC 入門

這篇文章還能夠在這裏找到 英語波蘭語ios

Learn the ins and outs of ARC in iOS 5!

Learn the ins and outs of ARC in iOS 5!git

這是iOS 5 盛宴中的第12篇教程! 這篇教程是咱們的新書 iOS 5 By Tutorials 的一個預覽章節。 Matthijs Hollemans 是這個章節的做者 – 也是 iOS Apprentice Series 的做者。 但願你喜歡!github

這篇文章發表自 iOS 教程團隊成員 Matthijs Hollemans, 一個經驗豐富的 iOS開發者和設計師。web

iOS 5 中最具爭議的一個新特性就是 Automatic Reference Counting (自動引用計數), 或者簡寫爲 ARC。 ARC 是 LLVM 3.0 編譯器的一個新特性,它完全拋棄了讓全部 iOS 開發者由愛生恨的手動內存管理機制。編程

在你的項目中使用 ARC 很是簡單。 除了再也不須要調用 retain, release 和 autorelease, 你能夠和日常同樣的開發。 基本上如此而已。後端

在開啓 Automatic Reference Counting 的狀況下, 編譯器將會自動地在你程序的正確位置插入 retain, release 和 autorelease 。你再也不須要爲這些事情操心了, 由於編譯器已經爲你作了這些事。 實際上, 使用 ARC 是如此的簡單,你甚至都不須要讀這篇教程。 ;-)數組

可是,若是你還對 ARC 有一些質疑 — 或許你不相信它能正確的處理全部事情, 或者你會認爲這樣會比手動內存管理執行的速度慢 — 那就繼續閱讀吧。 教程餘下的部分將會解開它神祕的面紗, 並向你展現哪些在使用 ARC 時,如何處理那些不太直觀的結果。安全

另外, 咱們將會手把手的教你如何將一個沒有使用 ARC 的應用轉換成使用 ARC 的。 你能夠用這些技術來將你現存的 iOS 項目轉換成使用 ARC。 幫助你減小大量的內存處理問題!服務器

它是怎麼工做的

你大概已經熟悉如何手工管理內存了, 就像這樣:網絡

  • 若是你想保持一個對象可用,除非它已經被 retain 了,不然你就須要 retain 它。
  • 若是你再也不須要一個對象了, 那麼你就須要 release 它, 除非它已經被 release 了 (經過 autorelease)。

做爲一個初學者, 你可能須要一個艱難的過程來了解這個概念, 但在一段時間以後, 它將成爲你的第二習慣。 如今你必須老是正確的在 retain 和 release 之間取得平衡。 除非你忘了。

手工內存管理的原則並不難,可是它很是容易出錯。 而且這些小錯誤可能會帶來嚴重的後果。 過分的 release 一個對象而且你的變量指向了一個已經無效的地址,你的應用就會在某一時刻崩潰掉,或者若是你沒有在該 release 的時候釋放掉內存,你的應用就會將內存佔滿。

Xcode 中的靜態分析是幫你找到這類問題的好幫手, 可是 ARC 作得更好。 經過自動地在合適的位置幫你插入 retain 和 release, 它徹底避免了內存管理中的問題!

有一個重要的一點須要知道的,就是 ARC 是 Objective-C 編譯器的一個特性, 全部 ARC 相關的處理都會發生在你構建你的應用的時候。 ARC 不是運行時特性(除了其中的一點, weak 指針系統)。 它也不是你在其餘編程語言中所瞭解的垃圾回收機制。

ARC 所作的事情就是在你編譯代碼的時候,在那些你須要本身處理內存管理的地方,插入 retain 和 release。 這就使得 ARC 和手工內存管理的速度同樣快, 有時還會更快一些, 由於它在後端進行了一些優化操做。

指針保持對象存在可用

你須要瞭解的 ARC 的新規則很簡單。 在手工內存管理中, 你須要 retain 一個對象,來讓他可用。 這再也不須要了, 你所須要作的僅僅是讓一個指針指向那個對象。 只要有變量指針指向那個對象, 那麼它就會一直在內存中。

當這個指針指向了另一個對象,或者再也不存在的時候, 和它相關聯的那個對象會被釋放掉。 這對全部類型的變量都適用: 實例變量, 屬性, 甚至局部變量。

用全部者的方式來想它,就更好理解了。 當你這樣作時,

NSString *firstName = self.textField.text;

firstName 變量成爲一個指向 NSString 對象的指針, 它指向了文本框中的內容。 firstName 變量如今就是這個字符串對象的全部者。

descr

一個對象能夠有多個全部者。 直到用戶改變了 UITextField 中的內容以前, 它的 text 屬性也是這個字符串對象的全部者。 有兩個指針指向了同一個對象:

descr

稍後,用戶會在文本框中輸入一些新的文字, 這時它的 text 屬性指向了一個新的字符串對象。 可是最初的那個字符串對象仍是有一個全部者(firstName 變量), 因此它仍然在內存中。

descr

只有當 firstName 也獲得一個新的值的時候,或者它超出了做用範圍 — 由於它是一個局部變量而且方法到告終尾,或者它是一個實例變量而且擁有它的對象被釋放了 — 這時全部關係就失效了。 這個字符串對象再也不有任何全部者, 它的 retain count 減小到 0,這個對象被釋放了。

descr

咱們稱 firstName 和 textField.text 爲 「strong」 類型的指針, 由於他們能保持對象的存活。 默認狀況下,全部的實例變量和局部變量都是 strong 指針。

也還有一個 「weak」 指針。 weak 類型的變量也能指向一個對象,可是他們不能成爲全部者:

__weak NSString *weakName = self.textField.text;

descr

weakName 變量和 textField.text 屬性指向了同一個字符串對象, 但它不是全部者。 若是文本框的內容改變了, 這個字符串對象就再也不有任何全部者了,而後就被釋放了:

descr

這個時候, weakName 變量的值會自動變成 nil。 它也被叫作 「zeroing」 weak 指針。

這個特性很是方便,由於它防止了指針指向了被釋放的內存。 這種狀況會致使不少 bug — 你可能據說過」懸空指針」或」殭屍」這樣的術語 — 但多虧了 zeroing weak 指針, 這些再也不是問題了!

你或許不會頻繁的使用到 weak 指針。 他們大多在兩個父子關係的對象上面比較有用。 父對象會有一個 strong 類型的指針指向子對象 — 所以它就」擁有」了子對象 — 可是爲了防止全部關係循環, 子對象僅僅有一個 weak 指針指向父對象。

這樣的一個例子是代理模式。 你的控制器擁有一個指向 UITableView 的 strong 指針。 Table View 的 datasource 和 delegate 反過來指向控制器,但用的是 weak 指針。 咱們稍後會詳細討論這個。

descr

下面這個很是有用:

__weak NSString *str = [[NSString alloc] initWithFormat:...];
 
NSLog(@"%@", str);  // will output "(null)"

這個 string 對象沒有任何全部者(由於 str 是 weak 的), 這個對象將會在建立後直接被釋放掉。 Xcode 將會給你一個警告, 由於這或許不是你想要的結果 (「Warning: assigning retained object to weak variable; object will be released after assignment」)。

你能夠用 __strong 關鍵字來表示一個變量是 strong 類型的指針:

__strong NSString *firstName = self.textField.text;

可是由於變量默認就是 strong 的,這樣作是多餘的。

屬性能夠是 strong 的,也能夠是 weak 的。 屬性的表示方法以下:

@property (nonatomic, strong) NSString *firstName;
 
@property (nonatomic, weak) id <MyDelegate> delegate;

ARC 很強大, 能夠真正的去掉你代碼中雜亂的部分。 你再也不須要考慮何時 retain 仍是 release, 只須要知道你的對象如何和其餘對象關聯起來。 你須要問一下你本身: 誰擁有什麼?

例如,在之前不可能像這樣寫代碼:

id obj = [array objectAtIndex:0];
 
[array removeObjectAtIndex:0];
 
NSLog(@"%@", obj);

在手工內存管理機制下, 從數組中刪除這個對象將會讓 obj 變量的內容失效。 一旦這個對象從數組中被刪除掉,它就會被釋放。 經過 NSLog() 來打印這個對象會致使應用崩潰。 在 ARC 中,上面的代碼會正常的工做。 由於咱們把這個對象賦值給 obj 變量, 它是一個 strong 指針, 數組再也不是這個對象惟一的全部者。 即便咱們從數組中把這個對象刪除掉, 這個對象仍然有效, 由於 obj 還在指向它。

Automatic Reference Counting 也還有一些不足。 做爲剛剛起步的特性, ARC 僅僅適用於 Objective-C 對象。 若是你的應用用到了 Core Foundation 或者 malloc() 和 free(), 你還須要負責內存管理。 咱們將會在這篇教程後面的部分看到一些例子。 另外, 爲了讓 ARC 正確的工做,一些語言規則會變得更嚴格。 僅有小小的犧牲, 你獲得的會比你付出的更多!

正由於 ARC 幫你在適當的位置處理了 retain 和 release, 但這並不表明你能夠徹底忘記內存管理機制。 由於 strong 指針可以保持對象存活, 仍然存在一些狀況,你須要手動的把這些指針設置爲 nil, 否則你的應用將會耗盡可用內存。若是你一直保持全部你建立的對象都存活,那麼 ARC 將不能釋放他們。 所以, 當你建立一個新對象的時候, 你還須要考慮誰擁有它,還有這個對象應該存活多久。

毫無疑問,ARC 將會是 Objective-C 的將來。 蘋果鼓勵開發者放棄手工內存管理,從而開始用 ARC 來寫他們的新應用。 它能帶來更簡單的源代碼和更健壯的應用。 在 ARC 中, 內存相關的崩潰已經成爲了過去。

可是,由於咱們成處於一個從手工向自動內存管理過渡的階段, 你會常常碰到那些還不兼容 ARC 的代碼, 不管是你本身的代碼,仍是第三方庫的。 幸運的是,你能夠將 ARC 和非 ARC 的代碼共同用於一個項目,我將給你展現幾個怎樣實現的方法。

ARC 甚至能和 C++ 很好的結合。 只須要遵照不多的一些限制,你還能夠在 iOS 4 中使用 ARC, 這個僅僅用於幫助加快它的採用進程。

一個聰明的開發者會試着儘量的讓他的工做自動化。 這正是 ARC 所提供的: 將你以前那些必須手動處理的初級編程工做自動化起來。 對我來講,這樣的轉變很容易。

應用

爲了展現如何在實踐中使用 Automatic Reference Counting。 我準備了一個簡單的項目,咱們將把它從手工內存管理轉換到 ARC。 這個應用,Artists, 由一個Table View 和一個 Search Bar 組成的界面。 當你在Search Bar 中輸入一些東西時, 這個應用調用 MusicBrainz API 來根據姓名搜索音樂家。

這個應用看起來是這樣:

descr

用他們本身的話說,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 對象:

descr

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 的上面, 在網絡請求完成以前, 阻止用戶的任何輸入:

descr

而後咱們建立 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 兼容:

  1. Xcode 有一個自動轉換工具, 他能夠遷移你的源代碼。
  2. 你能夠手工的轉換這些文件。
  3. 你能夠對那些你不想轉換的文件禁用 ARC。 這對那些你不想混在一塊兒的第三方庫很是有用。

咱們將在 Artists 應用中用到全部這些操做, 僅爲了向你展現全部這些是如何工做的。 在這個部分, 咱們將會經過 Xcode 的自動轉換工具來轉換源文件, 除了 MainViewController 和 AFHTTPRequestOperation。

在咱們作這些事情以前, 你應該把項目拷貝一份,由於這個工具會覆蓋原來的文件。 Xcode 會提供一個對源代碼的預覽界面, 可是爲了防止丟失,我不管如何也會備份一份。

ARC 是 LLVM 3.0 的一個新特性。 你現有的項目極可能用的是老的 GCC 4.2 或者 LLVM-GCC 編譯器, 因此首先要作的是將項目的編譯器切換到新版本,看一看編譯器是否在非 ARC 模式。 進入 Project Settings 界面,

選擇 Artists target, 在 Build Settings 選項卡中的搜索框中輸入 「compiler」. 這樣能夠過濾列表,展現出編譯選項:

descr

點選 Compiler for C/C++/Objective-C 選項,修改它爲 Apple LLVM compiler 3.0:

descr

在 Warnings 頭中,還要把 Other Linker Flags 設置爲 -Wall。 這樣編譯器將會檢測全部會致使問題的狀況。 默認狀況下,這些警告消息是被關閉的,可是我發現老是把他們打開而且將每個都看做是致命錯誤是頗有用的。 換句話說, 若是編譯器給出任何警告,我將會在繼續其餘工做以前修復它。 是否在你本身的項目中也這樣作徹底取決於你, 可是在轉換到 ARC 的過程當中, 我推薦你仔細看看編譯器給出的每個問題。

一樣地, 也要在 Build Options 頭中打開 Run Static Analyzer:

descr

Xcode 如今將會在每次構建項目的時候運行靜態分析。 這會讓構建的速度稍微慢一點, 可是做爲咱們這種規模的應用來講,這不算什麼。

讓咱們來構建一下應用, 看看新的編譯器會給出什麼問題. 首先咱們用 Product -> Clean(或 Shift-Cmd-K) 進行一次清理。 而後按下 Cmd-B 來構建應用。 Xcode 應該不會收到任何警告。 若是你在將你本身的項目轉換到 ARC, 而且收到了警告消息, 那麼如今正是修復他們的時候。

讓咱們把編譯器切換到 ARC 模式,而且再次構建應用。 咱們收到了一大堆錯誤消息, 如今正好能夠看看這些到底是什麼。

仍然在 Build Settings 屏幕中, 切換到 「All」 能夠看到全部可用的設置(Basic選項僅僅顯示最經常使用到的設置)。 搜索 「automatic」, 設置 Objective-C Automatic Reference Counting 選項爲 YES。 設置一個項目範圍的標記, 用來告訴 Xcode 你將要用 ARC 編譯器來編譯你的項目中全部的源文件。

descr

再次構建應用, 你應該會看到一大堆錯誤:

descr

很明顯,咱們要進行一些遷移! 大多數的錯誤都很明顯,他們說的都是你不能再用 retain,release 和 autorelease 了。 咱們能夠徹底手工的修正這些錯誤, 可是使用自動轉換工具會更容易一些。 這個工具會用 ARC 模式來編譯應用, 而後對每個它遇到的錯誤的地方進行重寫,直到項目再也不報錯。

在 Xcode 菜單中, 選擇 EditRefactorConvert to Objective-C ARC.。

descr

一個新的窗口會彈出來, 讓你選擇哪些部分是你想要轉換的:

descr

咱們不但願轉換整個項目, 只選擇下面這些文件:

  • main.m
  • AppDelegate.m
  • SVProgressHUD.m
  • SoundEffect.m

這個對話框顯示了一個小警告圖標,用來指示這個項目已經使用了 ARC。 這是由於咱們以前在 Build Settings 中開啓了 Objective-C Automatic Reference Counting 選項, 因此自動轉換工具會認爲它已是一個 ARC 項目了。 你能夠忽略這個警告, 他不會對轉換形成影響。

按下 Precheck 按鈕來開始轉換。 這個工具首先會檢測你的代碼是否處於足夠好的狀態來轉換到 ARC。 咱們以前用新的 LLVM 3.0 編譯器成功的構建了咱們的應用, 可是很明顯此次不行。 Xcode 會給出以下錯誤:

descr

它提示 「ARC readiness issues」, 咱們應該開啓 「Continue building after errors」 選項。 咱們應該先開啓這個選項。 打開 Xcode 的 Preferences 窗口 (在 Xcode 的菜單中), 進入 General 標籤。 開啓選項 「Continue building after errors」:

descr

讓咱們再試一次。選擇 EditRefactorConvert to Objective-C ARC 而且選中除了 MainViewController.m 和 AFHTTPRequestOperation.m 以外的全部源文件。 按下 Precheck 按鈕。

descr

很不幸, 咱們再一次獲得了一個錯誤。 和剛纔不一樣的是此次編譯器可以在轉換以前找出咱們須要修復的全部問題。 幸運的是, 只有一個錯誤:

descr

你可能會在這個列表中看到更多的錯我。 有時轉換工具會提示那些不徹底是 「ARC readiness」 性的錯誤。

咱們這個問題的完整描述是:

Cast of Objective-C pointer type 'NSURL *' to C pointer type 'CFURLRef' (aka 'const struct __CFURL *') requires a bridged cast

在源代碼編輯器中它看起來是這樣的:

descr

我會在後面更詳細的討論它, 但這裏的代碼想嘗試着把 NSURL 對象轉換成 CFURLRef 對象。 AudioServicesCreateSystemSoundID() 函數接受一個 CFURLRef 參數, 用來描述聲音文件的位置, 可是咱們給他的是一個 NSURL 對象。 CFURLRef and NSURL 是 「可互換的」, 這樣能夠在須要 CFURLRef 的地方使用 NSURL, 反過來也同樣。

通常狀況下, iOS 中基於 C 語言的 API 會用到 Core Foundation 對象 (這也是 CF 的意思), 而基於 Objective-C 的 API 用的是繼承自 NSObject 的」真正的」對象。 有時你須要在這二者之間進行轉換, 這也是 「可互換的」 對象所表明的意思。

然而, 當你使用 ARC 的時候,編譯器須要知道應該怎樣處理 「可互換的」 這些對象。 若是你在須要 CFURLRef 的地方使用了 NSURL, 那麼誰負責在最後釋放這些內存呢? 爲了解決這個難題, 一系列的新關鍵字被引入進來:

__bridge, __bridge_transfer 和 __bridge_retained。 咱們會在接下來的教程中深刻的瞭解如何使用它們。

如今咱們須要按照下面的方式修改源代碼:

OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)
 
  fileURL, &theSoundID);

預檢測可能會給出不止這一個錯誤。 你能夠安全的忽略它們, 上面對 SoundEffect.m 的修改是惟一一個咱們要作的。 轉換工具看起來對把什麼當作 「ARC 先決條件問題」 也不是很肯定。

讓咱們再運行一次轉換工具 – EditRefactorConvert to Objective-C ARC。 此次預檢測運行的沒有任何問題, 咱們將會獲得這樣的界面:

descr

點擊 Next 繼續。 幾秒鐘以後, Xcode 將會爲他要修改的全部文件和在這些文件上面進行的修改內容顯示一個預覽界面。 左邊的面板顯示的是修改過的文件,右邊的面板顯示的是原始文件。

descr

一個好的習慣是瀏覽一下全部這些文件,確保 Xcode 不會弄亂任何東西。讓咱們從頭至尾看一遍轉換工具給咱們展現的修改內容吧。

AppDelegate.h

@property (strong, nonatomic) UIWindow *window;
 
@property (strong, nonatomic) MainViewController *viewController;

App Delegate 有兩個屬性, 一個是 Window, 另外一個是 View Controller。 這個項目沒有包含 MainWindow.xib 文件, 因此這兩個對象由 AppDelegate 在 application:didFinishLaunchingWithOptions: 方法中建立, 並存放到屬性中以便進行內存管理。

這些屬性的聲明修改以下:

@property (retain, nonatomic)

修改成:

@property (strong, nonatomic)

strong 關鍵字和你想象的同樣。 它告訴 ARC 這個屬性對應的 synthesize 聲明的實例變量,持有一個當前對象的強引用。 換句話說, window 屬性包含了一個指向 UIWindow 對象的指針,而且做爲這個 UIWindow 對象的全部者。 只要 window 屬性還保留着它的值, 那麼這個 UIWindow 對象就一直存活。 viewController 屬性和 MainViewController 對象也是同樣的。

AppDelegate.m

在 AppDelegate.m 中, 建立 window 和 view controller 對象的代碼被修改了, 而且 dealloc 方法徹底被刪除了:

descr

看看他們之間不一樣的地方,

self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen]
 
  bounds]] autorelease];

和:

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen]
 
  bounds]];

這是沒問題的, 再也不須要調用 autorelease 了。 建立 view controller 的代碼也是同樣。

self.viewController = [[[MainViewController alloc] initWithNibName:
 
  @"MainViewController" bundle:nil] autorelease];

如今變成了:

self.viewController = [[MainViewController alloc] initWithNibName:
 
  @"MainViewController" bundle:nil];

在使用 ARC 以前, 若是你像這樣寫代碼,若是這個屬性聲明成 「retain」 類型的, 那麼就會形成內存泄露:

self.someProperty = [[SomeClass alloc] init];

init 方法返回了一個被 retain 過的對象,又把它賦值給聲明爲 retain 的屬性。 這也是爲何你必需要調用 autorelease, 用來和 init 方法中的 retain 進行平衡。 可是在 ARC 中,上面的代碼是沒問題的。 編譯器足夠的只能,能夠發現它不該該被 retain 兩次。

ARC 一個讓我喜歡的地方是它徹底再也不須要寫 dealloc 方法了。 當一個對象被釋放時, 它的實例變量和屬性也會自動跟着釋放。 你再也不須要寫這些東西了:

- (void)dealloc
 
{
 
     [_window release];
 
     [_viewController release];
 
     [super dealloc];
 
}

由於 Objective-C 自動的處理了這些問題。 事實上, 像上面這樣寫代碼也是不可能的。 在 ARC 中,你是不容許調用 release 和 [super dealloc] 消息的。 你仍然能夠實現 dealloc 方法 — 而且後面你會看到一個例子 — 但這裏再也不須要手動的釋放你的實例變量。

自動轉換工具不會幫你把 AppDelegate 從 NSObject 的子類轉換爲 UIResponder 的子類。 當你用新的 Xcode 模板來建立項目的時候, AppDelegate 類已是 UIResponder 的子類了。 讓他繼承自 NSObject 也沒什麼壞處, 可是若是你願意,你能夠將它繼承自 UIResponder:

@interface AppDelegate : UIResponder <UIApplicationDelegate>

Main.m

在手工內存管理的應用中, [autorelease] 方法和 「autorelease pool」 一同工做, 它用 NSAutoreleasePool 對象來表示。 甚至 main.m 中也有一個, 若是你直接操做線程, 那麼必須爲每一個線程建立一個你本身的 NSAutoreleasePool。 有時,開發者還會將 NSAutoreleasePool 放到一個工做量很大的循環中, 用來保證循環中的自動釋放對象不會佔據很大的內存,而且每次循環完成後都會被清楚掉。

在 ARC 中 autorelease 依然存在,即便你不須要直接的調用 [autorelease] 方法。 每次你用不是以 alloc, init, copy, mutableCopy 或 new 開頭的方法返回一個對象時, ARC 編譯器會自動幫你調用 autorelease 方法。 這些對象還會在 autorelease pool 中被釋放。 一個比較大的差異是 NSAutoreleasePool 被寫成了一個新的語法, @autoreleasepool。

descr

自動轉換工具把咱們的 main() 函數從這樣:

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
 
int retVal = UIApplicationMain(argc, argv, nil,
 
  NSStringFromClass([AppDelegate class]));
 
[pool release];
 
return retVal;

轉換成這樣:

@autoreleasepool {
 
  int retVal = UIApplicationMain(argc, argv, nil,
 
    NSStringFromClass([AppDelegate class]));
 
    return retVal;
 
}

不只對咱們開發人員來講,它更容易閱讀,並且在它的內部也進行了不少修改, 讓這個新的 autorelease pool 比以前的快不少。 除非你在以前本身的代碼中用到了 NSAutoreleasePool, 在 ARC 中你幾乎不用再關心 autorelease 了, 你須要把它替換成 @autoreleasepool 代碼塊。 轉換工具會自動的幫你完成這個工做。

SoundEffect.m

這個文件沒怎麼修改, 只是刪除了 [super dealloc] 的調用。 你不能再在 dealloc 方法中調用 super 了。

descr

注意 dealloc 方法在這裏仍然是必須的。 在你的大多數類中你能夠直接忘掉 dealloc, 讓編譯器替你處理這些事情。 然而,有時候, 你須要手工的釋放一些資源。 這個類就屬於這種狀況。 當 SoundEffect 被釋放的時候, 咱們須要調用 AudioServicesDisposeSystemSoundID() 方法來清理 sound 對象並釋放它。

SVProgressHUD.m

這個文件是這幾個裏面修改最多的, 但都比較瑣碎。

descr

在 SVProgressHUD.m 的頂部,你須要找到稱爲 「類擴展」 的代碼, @interface SVProgressHUD (), 這裏有一些屬性的聲明。 若是你不熟悉類擴展, 他們其實和 Category 很像,但有一些特殊的能力。 類擴展的定義和 Category 很類似, 可是它在 () 之間沒有任何名字。 類擴展能夠帶有屬性和實例變量, 而 Category 沒有這些, 你僅僅能在這個類的 .m 文件中引用它們。 (話句話說,你不能在其餘類中引用這個類擴展) 。

類擴展最酷的地方是,它能讓你在你的類中增長私有屬性和方法。 若是你不想在 public @interface 中暴露一些屬性和方法, 那麼你就能夠把它們放到類擴展中。 這也是 SVProgressHUD 作的事情。

@interface SVProgressHUD ()
 
 
 
...
 
@property (nonatomic, strong) NSTimer *fadeOutTimer;
 
@property (nonatomic, strong) UILabel *stringLabel;
 
@property (nonatomic, strong) UIImageView *imageView;
 
@property (nonatomic, strong) UIActivityIndicatorView *spinnerView;
 
...
 
 
 
@end

像咱們以前看到的, retain 的屬性如今變成了 strong。 若是你瀏覽一下預覽視圖, 你會看到其餘的更改只是簡單的刪除掉 retain 和 release 語句。

實際的應用它

若是你對轉換工具作的修改還滿意, 按下 Save 按鈕,讓他生效。 Xcode 首先會問題是否但願在進行文件修改以前保存一下項目的快照:

descr

你應該點 Enable。 若是你退回到原來的代碼, 你能夠在 Organizer 中找到項目的快照。

當 ARC 轉換工具完成後, 按下 Cmd+B 來構建應用。 構建應該成功的完成。 但會有一些關於 SVProgressHUD.m 的新警告:

descr

注意到在這個類中仍然用到了 dealloc 方法, 在這裏中止了 timer 而且從 NSNotificationCenter 中註銷了通知。 很明顯,這些是 ARC 不能幫你作的。

包含警告的這行代碼看起來是這樣:

if(fadeOutTimer != nil)
 
     [fadeOutTimer invalidate], [fadeOutTimer release], fadeOutTimer = nil;

如今是:

if(fadeOutTimer != nil)
 
     [fadeOutTimer invalidate], fadeOutTimer, fadeOutTimer = nil;

工具刪除了對 [release] 的調用, 可是它把變量還留在那。 一個單獨的變量放在那沒有任何用處,因此 Xcode 發出了警告。 這種狀況自動轉換工具是不能預知的。

若是你對逗號這種語法看到困惑, 那麼你要知道,在 Objective-C 用逗號將多個表達式鏈接成一個語句是有效的。 上面是一個釋放對象和設置相應的實例變量爲空時的一個通用技巧。 由於全部事情都在一行代碼中處理了。 因此不須要花括號。

你能夠修改這行代碼來消除警告:

if(fadeOutTimer != nil)
 
    [fadeOutTimer invalidate], fadeOutTimer = nil;

從技術上說咱們不須要這個 fadeOutTimer = nil; 在 dealloc 中, 對象會在被刪除時釋放它全部的實例變量。 但在其餘的方法中, 當 timer 失效以後, 你必須設置 fadeOutTimer 爲 nil。 若是你不這樣作, SVProgressHUD 會長時間等待這個失效的 timer。

再次構建應用, 此次不會有任何警告。 轉換成功了!

可是,等一下, 咱們在轉換時跳過了 MainViewController 和 AFHTTPRequestOperation。 他們是如何在編譯過程當中沒有任何問題的? 當咱們以前試着用 ARC 來構建項目時, 在這些文件中會出現不少錯誤。

答案很簡單: 轉換工具爲這兩個源文件關閉了 ARC 功能。 你能夠在 Project Settings 的 Build Phases 標籤中看到他們:

descr

咱們以前經過在 Build Settings 中設置 Objective-C Automatic Reference Counting 選項爲 YES, 在項目全局開啓了 ARC。 可是你也能夠告訴編譯器一些例外,讓它忽略一些指定的文件, 經過 -fno-objc-arc 標記。 Xcode 將會爲這些文件關閉 ARC。

由於,不可能期待開發者一次性的將整個項目遷移到 ARC 中, Apple 的員工們讓 ARC 和非 ARC 的代碼能夠在同一個項目中工做。 提示: 一個簡單的方式是,直接經過轉換工具遷移那些你想轉換的文件,而後它會自動爲其他的文件增長 -fno-objc-arc 標記。 你也能夠手工的添加這些標記, 但當你有不少文件的時候,這會很無聊。

相關文章
相關標籤/搜索