ARC初步介紹

[轉載自 http://onevcat.com/2012/06/arc-hand-by-hand/]html

手把手教你ARC——iOS/Mac開發ARC入門和使用

Revolution of Objective-c

Revolution of Objective-cgit

本文部分實例取自iOS 5 Toturail一書中關於ARC的教程和公開內容,僅用於技術交流和討論。請不要將本文的部分或所有內容用於商用,謝謝合做。github

歡迎轉載本文,可是轉載請註明本文出處:http://www.onevcat.com/2012/06/arc-hand-by-hand/web

本文適合人羣:對iOS開發有必定基礎,熟悉iOS開發中內存管理的Reference Counting機制,對ARC機制有聽聞很嚮往可是一直因爲種種緣由沒有使用的童鞋。本文將從ARC機理入手對這個解放廣大iOS開發者的偉大機制進行一個剖析,並逐步引導你開始使用ARC。一旦習慣ARC,你必定會被它的簡潔高效所征服。objective-c

寫在開頭

雖然距離WWDC2011和iOS 5已經快一年時間,可是不少開發者並無利用新方法來提升本身的水平,這點在ARC的使用上很是明顯(特別是國內,基本不多見到同行轉向ARC)。我曾經詢問過一些同行爲何不轉向使用ARC,不少人的回答是擔憂內存管理不受本身控制..其實我我的認爲這是對於ARC機制瞭解不足從而不自信,所致使的對新事物的恐懼。而做爲最須要「追趕時髦」的職業,這樣的心態將至關不利。謹以此文但願能清楚表述ARC的機理和用法,也但願可以成爲如今中文入門教學缺失的補充。設計模式


什麼是ARC

Automatic Reference Counting,自動引用計數,即ARC,能夠說是WWDC2011和iOS5所引入的最大的變革和最激動人心的變化。ARC是新的LLVM 3.0編譯器的一項特性,使用ARC,能夠說一舉解決了廣大iOS開發者所憎恨的手動內存管理的麻煩。數組

在工程中使用ARC很是簡單:只須要像往常那樣編寫代碼,只不過永遠不寫retain,releaseautorelease三個關鍵字就好~這是ARC的基本原則。當ARC開啓時,編譯器將自動在代碼合適的地方插入retain,releaseautorelease,而做爲開發者,徹底不須要擔憂編譯器會作錯(除非開發者本身錯用ARC了)。好了,ARC至關簡單吧~到此爲止,本教程結束。安全

等等…也許還有其餘問題,最嚴重的問題是「我怎麼肯定讓ARC來管理不會出問題?」或者「用ARC會讓程序性能降低吧」。對於ARC不能正處理內存管理的質疑自從ARC出生以來就一直存在,而如今愈來愈多的代碼轉向ARC並取得了很好的效果,這證實了ARC是一套有效的簡化開發複雜程度的機制,另外經過研究ARC的原理,能夠知道使用ARC甚至能提升程序的效率。在接下來將詳細解釋ARC的運行機理而且提供了一個step-by-step的教程,將非ARC的程序轉換爲ARC。網絡


ARC工做原理

手動內存管理的機理你們應該已經很是清楚了,簡單來講,只要遵循如下三點就能夠在手動內存管理中避免絕大部分的麻煩:app

若是須要持有一個對象,那麼對其發送retain 若是以後再也不使用該對象,那麼須要對其發送release(或者autorealse) 每一次對retain,alloc或者new的調用,須要對應一次release或autorealse調用

初學者可能僅僅只是知道這些規則,可是在實際使用時不免犯錯。可是當開發者常用手動引用計數 Manual Referecen Counting(MRC)的話,這些規則將逐漸變爲本能。你會發現少一個release的代碼怎麼看怎麼彆扭,從而減小或者杜絕內存管理的錯誤。能夠說MRC的規則很是簡單,可是同時也很是容易出錯。每每很小的錯誤就將引發crash或者OOM之類的嚴重問題。

在MRC的年代裏,爲了不不當心忘寫release,Xcode提供了一個很實用的小工具來幫助可能存在的代碼問題(Xcode3裏默認快捷鍵Shift+A?不記得了),能夠指出潛在的內存泄露或者過多釋放。而ARC在此基礎上更進一步:ARC是Objective-C編譯器的特性,而不是運行時特性或者垃圾回收機制,ARC所作的只不過是在代碼編譯時爲你自動在合適的位置插入releaseautorelease,就如同以前MRC時你所作的那樣。所以,至少在效率上ARC機制是不會比MRC弱的,而由於能夠在最合適的地方完成引用計數的維護,以及部分優化,使用ARC甚至能比MRC取得更高的運行效率。

ARC機制

學習ARC很簡單,在MRC時代你須要本身retain一個想要保持的對象,而如今不須要了。如今惟一要作的是用一個指針指向這個對象,只要指針沒有被置空,對象就會一直保持在堆上。當將指針指向新值時,原來的對象會被release一次。這對實例變量,synthesize的變量或者局部變量都是適用的。好比

1
NSString *firstName = self.textField.text;

firstName如今指向NSString對象,這時這個對象(textField的內容字符串)將被hold住。好比用字符串@「OneV」做爲例子(雖然實際上不該該用字符串舉例子,由於字符串的retainCount規則其實和普通的對象不同,你們就把它看成一個普通的對象來看吧…),這個時候firstName持有了@」OneV」。

一個strong指針

一個strong指針

固然,一個對象能夠擁有不止一個的持有者(這個相似MRC中的retainCount>1的狀況)。在這個例子中顯然self.textField.text也是@「OneV」,那麼如今有兩個指針指向對象@」OneV」(被持有兩次,retainCount=2,其實對NSString對象說retainCount是有問題的,不過anyway~就這個意思而已.)。

兩個strong指向同一個對象

兩個strong指向同一個對象

過了一下子,也許用戶在textField裏輸入了其餘的東西,那麼self.textField.text指針顯然如今指向了別的字符串,好比@「onevcat」,可是這時候原來的對象已然是存在的,由於還有一個指針firstName持有它。如今指針的指向關係是這樣的:

其中一個strong指向了另外一個對象

其中一個strong指向了另外一個對象

只有當firstName也被設定了新的值,或者是超出了做用範圍的空間(好比它是局部變量可是這個方法執行完了或者它是實例變量可是這個實例被銷燬了),那麼此時firstName也再也不持有@「OneV」,此時再也不有指針指向@」OneV」,在ARC下這種情況發生後對象@」OneV」即被銷燬,內存釋放。

沒有strong指向@"OneV",內存釋放

沒有strong指向@"OneV",內存釋放

相似於firstNameself.textField.text這樣的指針使用關鍵字strong進行標誌,它意味着只要該指針指向某個對象,那麼這個對象就不會被銷燬。反過來講,ARC的一個基本規則便是,只要某個對象被任一strong指針指向,那麼它將不會被銷燬。若是對象沒有被任何strong指針指向,那麼就將被銷燬。在默認狀況下,全部的實例變量和局部變量都是strong類型的。能夠說strong類型的指針在行爲上和MRC時代retain的property是比較類似的。

既然有strong,那確定有weak咯~weak類型的指針也能夠指向對象,可是並不會持有該對象。好比:

1
__weak NSString *weakName = self.textField.text

獲得的指向關係是:

一個strong和一個weak指向同一個對象

一個strong和一個weak指向同一個對象

這裏聲明瞭一個weak的指針weakName,它並不持有@「onevcat」。若是self.textField.text的內容發生改變的話,根據以前提到的「只要某個對象被任一strong指針指向,那麼它將不會被銷燬。若是對象沒有被任何strong指針指向,那麼就將被銷燬」原則,此時指向@「onevcat」的指針中沒有strong類型的指針,@」onevcat」將被銷燬。同時,在ARC機制做用下,全部指向這個對象的weak指針將被置爲nil。這個特性至關有用,相信無數的開發者都曾經被指針指向已釋放對象所形成的EXC_BAD_ACCESS困擾過,使用ARC之後,不管是strong仍是weak類型的指針,都再也不會指向一個dealloced的對象,從根源上解決了意外釋放致使的crash

strong指向另外對象,內存釋放,weak自動置nil

strong指向另外對象,內存釋放,weak自動置nil

不過在大部分狀況下,weak類型的指針可能並不會很經常使用。比較常見的用法是在兩個對象間存在包含關係時:對象1有一個strong指針指向對象2,並持有它,而對象2中只有一個weak指針指回對象1,從而避免了循環持有。一個常見的例子就是oc中常見的delegate設計模式,viewController中有一個strong指針指向它所負責管理的UITableView,而UITableView中的dataSourcedelegate指針都是指向viewController的weak指針。能夠說,weak指針的行爲和MRC時代的assign有一些類似點,可是考慮到weak指針更聰明些(會自動指向nil),所以仍是有所不一樣的。細節的東西咱們稍後再說。

一個典型的delegate設計模式

一個典型的delegate設計模式

注意相似下面的代碼彷佛是沒有什麼意義的:

1
2
__weak NSString *str = [[NSString alloc] initWithFormat:]; NSLog(@"%@",str); //輸出是"(null)" 

因爲strweak,它不會持有alloc出來的NSString對象,所以這個對象因爲沒有有效的strong指針指向,因此在生成的同時就被銷燬了。若是咱們在Xcode中寫了上面的代碼,咱們應該會獲得一個警告,由於不管什麼時候這種狀況彷佛都是不太可能出現的。你能夠把weak換成strong來消除警告,或者直接前面什麼都不寫,由於ARC中默認的指針類型就是strong

property也能夠用strongweak來標記,簡單地把原來寫retainassign的地方替換成strong或者weak就能夠了。

1
2
@property (nonatomic, strong) NSString *firstName; @property (nonatomic, weak) id delegate; 

ARC能夠爲開發者節省不少代碼,使用ARC之後不再須要關心何時retain,何時release,可是這並不意味你能夠不思考內存管理,你可能須要常常性地問本身這個問題:誰持有這個對象?

好比下面的代碼,假設array是一個NSMutableArray而且裏面至少有一個對象:

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

在MRC時代這幾行代碼應該就掛掉了,由於array中0號對象被remove之後就被當即銷燬了,所以obj指向了一個dealloced的對象,所以在NSLog的時候將出現EXC_BAD_ACCESS。而在ARC中因爲obj是strong的,所以它持有了array中的首個對象,array再也不是該對象的惟一持有者。即便咱們從array中將obj移除了,它也依然被別的指針持有,所以不會被銷燬。

一點提醒

ARC也有一些缺點,對於初學者來講,可能僅只能將ARC用在objective-c對象上(也即繼承自NSObject的對象),可是若是涉及到較爲底層的東西,好比Core Foundation中的malloc()或者free()等,ARC就鞭長莫及了,這時候仍是須要本身手動進行內存管理。在以後咱們會看到一些這方面的例子。另外爲了確保ARC能正確的工做,有些語法規則也會由於ARC而變得稍微嚴格一些。

ARC確實能夠在適當的地方爲代碼添加retain或者release,可是這並不意味着你能夠徹底忘記內存管理,由於你必須在合適的地方把strong指針手動設置到nil,不然app極可能會oom。簡單說仍是那句話,你必須時刻清醒誰持有了哪些對象,而這些持有者在何時應該變爲指向nil

ARC必然是Objective-C以及Apple開發的趨勢,從此也會有愈來愈多的項目採用ARC(甚至不排除MRC在將來某個版本被棄用的可能),Apple也一直鼓勵開發者開始使用ARC,由於它確實能夠簡化代碼並加強其穩定性。能夠這麼說,使用ARC以後,因爲內存問題形成的crash基本就是過去式了(OOM除外 :P)

咱們正處於由MRC向ARC轉變的節點上,所以可能有時候咱們須要在ARC和MRC的代碼間來回切換和適配。Apple也想到了這一點,所以爲開發這提供了一些ARC和非ARC代碼混編的機制,這些也將在以後的例子中列出。另外ARC甚至能夠用在C++的代碼中,而經過遵照一些代碼規則,iOS 4裏也可使用ARC(雖然我我的認爲在如今iOS 6都呼之欲出的年代已經基本沒有須要爲iOS 4作適配的必要了)、

總之,聰明的開發者總會嘗試儘量的自動化流程,已減輕本身的工做負擔,而ARC偏偏就爲咱們提供了這樣的好處:自動幫咱們完成了不少之前須要手動完成的工做,所以對我來講,轉向ARC是一件不須要考慮的事情。


具體操做

說了這麼多,終於能夠實踐一下了。在決定使用ARC後,不少開發者面臨的首要問題是不知如何下手。由於可能手上的項目已經用MRC寫了一部分,不想麻煩作轉變;或者由於新項目裏用ARC時遇到了奇怪的問題,從而放棄ARC退回MRC。這都是常見的問題,而在下面,將經過一個demo引導你們完全轉向ARC的世界。

Demo

Demo

Demo

例子很簡單,這是一個查找歌手的應用,包含一個簡單的UITableView和一個搜索框,當用戶在搜索框搜索時,調用MusicBrainz的API完成名字搜索和匹配。MusicBrainz是一個開放的音樂信息平臺,它提供了一個免費的XML網頁服務,若是對MusicBrainz比較有興趣的話,能夠到它的官網逛一逛。

Demo的起始例子能夠從這裏下載,爲了照顧新人,在這邊進行簡單說明。在Xcode中打開下載的例子,應該能夠看到以下內容(Xcode和iOS開發熟練者請跳過此段)

AppDelegate.h/m 這是整個app的delegate,沒什麼特殊的,每一個iOS/Mac程序在main函數之後的入口,由此進入app的生命週期。在這裏加載了最初的viewController並將其放到Window中展現出來。另外appDelegate還負責處理程序開始退出等系統委託的事件

MainViewController.h/m/xib 這個demo最主要的ViewController,含有一個TableView和一個搜索條。 SoundEffect.h/m 簡單的播放聲音的類,在MusicBrainz搜索完畢時播放一個音效。 main.m 程序入口,全部c程序都從main函數開始執行

AFHTTPRequestOperation.h/m 這是有名的網絡框架AFNetworking的一部分,用來幫助等簡單地處理web服務請求。這裏只包含了這一個類而沒有將所有的AFNetworking包括進來,由於咱們只用了這一個類。完整的框架代碼能夠在github的相關頁面上找到https://github.com/gowalla/AFNetworking

SVProgresHUD.h/m/bundle 是一個經常使用的進度條指示,當搜索的時候出現以提示用戶正在搜索請稍後。bundle是資源包,裏面包含了幾張該類用到的圖片,打進bundle包的目的一方面是爲了資源容易管理,另外一方面也是主要方面時爲了避免和其餘資源發生衝突(Xcode中資源名字是資源的惟一標識,同名字的資源只能出現一次,而放到bundle包裏能夠避免這個潛在的問題)。SVProgresHUD能夠在這裏找到https://github.com/samvermette/SVProgressHUD

快速過一遍這個應用吧:MainViewControllerUIViewController的子類,對應的xib文件定義了對應的UITableViewUISearchBarTableView中顯示searchResult數組中的內容。當用戶搜索時,用AFHTTPRequestOperation發一個HTTP請求,當從MusicBrainz獲得迴應後將結果放入searchResult數組中並用tableView顯示,當返回結果是空時在tableView中顯示沒找到。主要的邏輯都在MainViewController.m中的-searchBarSearchButtonClicked:方法中,生成了用於查詢的URL,根據MusicBrainz的需求替換了請求的header,而且完成了返回邏輯,而後在主線程中刷新UI。整個程序仍是比較簡單的~

MRC到ARC的自動轉換

回到正題,咱們討論的是ARC,關於REST API和XML解析的技術細節就暫時先忽略吧..整個程序都是用MRC來進行內存管理的,首先來讓咱們把這個demo轉成ARC吧。基本上轉換爲ARC意味着把全部的retain,releaseautorelease關鍵字去掉,在以前咱們明確幾件事情:

  • Xcode提供了一個ARC自動轉換工具,能夠幫助你將源碼轉爲ARC
  • 固然你也能夠本身動手完成ARC轉換
  • 同時你也能夠指定對於某些你不想轉換的代碼禁用ARC,這對於不少龐大複雜的尚未轉至ARC的第三方庫幫助很大,由於不是你寫的代碼你想動手修改的話代碼超級容易mess…

對於咱們的demo,爲了說明問題,這三種策略咱們都將採用,注意這僅僅只是爲了展現如何轉換。實際操做中不須要這麼麻煩,並且從此的絕大部分狀況應該是從工程創建開始就是ARC的。

選擇LLVM compiler 3.0

選擇LLVM compiler 3.0

首先,ARC是LLVM3.0編譯器的特性,而老的工程特別是Xcode3時代的工程的默認編譯器極可能是GCC或者LLVM-GCC,所以第一步就是確認編譯器是否正確。在Project設置面板,選擇target,在Build Settings中將Compiler for C/C++/Objective-C選爲Apple LLVM compiler 3.0或以上。爲了確保以後轉換的順利,在這裏我我的建議最好把Treat Warnings as Errors和 Run Static Analyzer都打開,確保在改變編譯器後代碼依舊沒有警告或者內存問題(雖然靜態分析可能不太能保證這一點,可是聊勝於無)。好了~clean(Shift+Cmd+K)之後Bulid一下試試看,通過修改後的demo工程沒有任何警告和錯誤,這是很好的開始。(對於存在警告的代碼,這裏是很好的修復的時機..請在轉換前確保原來的代碼沒有內存問題)。

打開ARC

打開ARC

接下來就是完成從MRC到ARC的偉大轉換了。仍是在Build Settings頁面,把Objective-C Automatic Reference Counting改爲YES(若是找不到的話請看一看搜索欄前面的小標籤是否是調成All了..這個選項在Basic裏是不出現的),這樣咱們的工程就將在全部源代碼中啓用ARC了。而後…試着編譯一下看看,嗯..無數的錯誤。

請耐心聆聽編譯器的傾訴,由於不少時候它是你惟一的夥伴

請耐心聆聽編譯器的傾訴,由於不少時候它是你惟一的夥伴

這是很正常的,由於ARC裏不容許出現retain,release之類的,而MRC的代碼這些是確定會有的東西。咱們能夠手動一個一個對應地去修復這些錯誤,可是這很麻煩。Xcode爲咱們提供了一個自動轉換工具,能夠幫助重寫源代碼,簡單來講就是去掉多餘的語句而且重寫一些property關鍵字。

使用Xcode自帶的轉換ARC工具

使用Xcode自帶的轉換ARC工具

選擇要轉換的文件

選擇要轉換的文件

這個小工具是Edit->Refactor下的Convert to Objective-C ARC,點擊後會讓咱們選擇要轉換哪幾個文件,在這裏爲了說明除了自動轉換外的方法,咱們不所有轉換,而只是選取其中幾個轉換(MainViewController.mAFHTTPRequestOperation.m不作轉換,以後咱們再手動將這兩個轉爲ARC)。注意到這個對話框上有個警告標誌告訴咱們target已是ARC了,這是因爲以前咱們在Build Settings裏已經設置了啓用ARC,其實直接在這裏作轉換後Xcode會自動幫咱們開啓ARC。點擊檢查後,Xcode告訴咱們一個不幸的消息,不能轉換,須要修復ARC readiness issues..後面還告訴咱們要看到全部的所謂的ARC readiness issues,能夠到設置的General裏把Continue building after errors勾上…What the f**k…好吧~先乖乖遵從Xcode的建議」Cmd+,「而後Continue building after errors打勾而後再build。

乖乖聽話,去把勾打上

乖乖聽話,去把勾打上

問題依舊,不過在issue面板裏應該能夠看到全部出問題的代碼了。在咱們的例子裏,問題出在SoundEffect.m裏:

1
2
3
4
5
6
7
8
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil]; if (fileURL != nil) {  SystemSoundID theSoundID;  OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)fileURL, &theSoundID);  if (error == kAudioServicesNoError) {  soundID = theSoundID;  } } 

這裏代碼嘗試把一個NSURL指針強制轉換爲一個CFURLRef指針。這裏涉及到一些Core Services特別是Core Foundation(CF)的東西,AudioServicesCreateSystemSoundID()函數接受CFURLRef爲參數,這是一個CF的概念,可是咱們在較高的抽象層級上所創建的是NSURL對象。在Cocoa框架中,有不少頂層對象對底層的抽象,而在使用中咱們每每能夠不加區別地對這兩種對象進行一樣的對待,這類對象即爲能夠」自由橋接」的對象(toll-free bridged)。NSURL和CFURLRef就是一對好基友好例子,在這裏其實CFURLRefNSURL是能夠進行替換的。

一般來講爲了代碼在底層級上的正確,在iOS開發中對基於C的API的調用所傳入的參數通常都是CF對象,而Objective-C的API調用都是傳入NSObject對象。所以在採用自由橋接來調用C API的時候就須要進行轉換。可是在使用ARC編譯的時候,由於內存管理的緣由,編譯器須要知道對這些橋接對象要實行什麼樣的操做。若是一個NSURL對象替代了CFURLRef,那麼在做用區域外,應該由誰來決定內存釋放和對象銷燬呢?爲了解決這個問題,引入了bridge,bridge_transfer和__bridge_retained三個關鍵字。關於選取哪一個關鍵字作轉換,須要由實際的代碼行爲來決定。若是對於自由橋接機制感興趣,你們能夠本身找找的相關內容,好比適用類型內部機制一個簡介~以後我也會對這個問題作進一步說明

回到demo,咱們如今在上面的代碼中(CFURLRef)前加上__bridge進行轉換。而後再運行ARC轉換工具,這時候檢查應該沒有其餘問題了,那麼讓咱們進行轉換吧~固然在真正轉換以前會有一個預覽界面,在這裏咱們最好檢查一下轉換是否是都按照預想進行了..要是出現大面積錯誤又沒有備份或者出現各類意外的話就能夠哭了…

先後變化的話比較簡單,基本就是去掉不須要的代碼和改變property的類型而已,其實有信心的話不太須要每次都看,可是若是是第一次執行ARC轉換的操做的話,我仍是建議稍微看一下變化,這樣能對ARC有個直觀上的瞭解。檢查一遍,應該沒什麼問題了..須要注意的是main.m裏關於autoreleasepool的變化以及全部dealloc調用裏的[super dealloc]的刪除,它們一樣是MRC到ARC的主要變化..

好了~轉換完成之後咱們再build看看..應該會有一些警告。對於原來retain的property,比較保險的作法是轉爲strong,在LLVM3.0中自動轉換是這樣作的,可是在3.1中property默認並非strong,這樣在使用property賦值時存在警告,咱們在property聲明裏加上strong就行了~而後就是SVProgressHUD.m裏可能存在問題,這是因爲原做者把release的代碼和其餘代碼寫在一行了.致使自動轉換時只刪掉了部分,而留下了部分不該該存在的代碼,刪掉對變量的空的調用就行了..

自動轉換以後的故事

而後再編譯,沒有任何錯誤和警告了,好棒~等等…咱們剛纔沒有對MainViewController和AFHTTPRequestOperation進行處理吧,那麼這兩個文件裏應該還存在release之類的東西吧..?看一看這兩個文件,果真有各類release,可是爲何能編譯經過呢?!明明剛纔在自動轉換前他們還有N多錯的嘛…答案很簡單,在自動轉換的時候由於咱們沒有勾選這兩個文件,所以編譯器在自動轉換事後爲這兩個文件標記了」不使用ARC編譯」。能夠看到在target的Building Phases下,MainViewController.m和AFHTTPRequestOperation.m兩個文件後面被加上了-fno-objc-arc的編譯標記,被加上該標記的文件將不使用ARC規則進行編譯。(相對地,若是你想強制對某幾個文件啓用ARC的話,能夠爲其加上-fobjc-arc標記)

強制不是用ARC

強制不是用ARC

提供這樣的編譯標記的緣由是顯而易見的,由於老是有一部分的第三方代碼並無轉換爲ARC(多是因爲維護者犯懶或者已經終止維護),因此對於這部分代碼,爲了迅速完成轉換,最好是使用-fno-objc-arc標記來禁止在這些源碼上使用ARC。

爲了方便查找,再此列出一些在轉換時可能出現的問題,固然在咱們使用ARC時也須要注意避免代碼中出現這些問題:

  • 「Cast … requires a bridged cast」

這是咱們在demo中遇到的問題,再也不贅述

  • Receiver type ‘X’ for instance message is a forward declaration

這每每是引用的問題。ARC要求完整的前向引用,也就是說在MRC時代可能只須要在.h中申明@class就能夠,可是在ARC中若是調用某個子類中未覆蓋的父類中的方法的話,必須對父類.h引用,不然沒法編譯。

  • Switch case is in protected scope

如今switch語句必須加上{}了,ARC須要知道局部變量的做用域,加上{}後switch語法更加嚴格,不然遇到沒有break的分支的話內存管理會出現問題。

  • A name is referenced outside the NSAutoreleasePool scope that it was declared in…

這是因爲寫了本身的autoreleasepool,而在轉換時在原來的pool中申明的變量在新的@autoreleasepool中做用域將被侷限。解決方法是把變量申明拿到pool的申請以前。

  • ARC forbids Objective-C objects in structs or unions

能夠說ARC所引入的最嚴格的限制是不能在C結構體中放OC對象了..所以相似下面這樣的代碼是不可用的

1
2
3
4
typedef struct {  UIImage *selectedImage;  UIImage *disabledImage; } ButtonImages; 

這個問題只有乖乖想辦法了..改變原來的結構什麼的..

手動轉換

剛纔作了對demo的大部分轉換,還剩下了MainViewController和AFHTTPRequestOperation是MRC。可是因爲使用了-fno-objc-arc,所以如今編譯和運行都沒有問題了。下面咱們看看如何手動把MainViewController轉爲ARC,這也有助於進一步理解ARC的規則。

首先,咱們須要轉變一下觀念…對於MainViewController.h,在.h中申明瞭兩個實例變量:

1
2
3
4
5
@interface MainViewController : UIViewController {  NSOperationQueue *queue;  NSMutableString *currentStringValue; } 

咱們不妨仔細考慮一下,爲何在interface裏出現了實例變量的申明?一般來講,實例變量只是在類的實例中被使用,而你所寫的類的使用者並無太多必要了解你的類中有哪些實例變量。而對於絕大部分的實例變量,應該都是protected或者private的,對它們的操做只應該用settergetter,而這正是property所要作的工做。能夠說,將實例變量寫在頭文件中是一種遺留的陋習。更好的寫實例變量名字的地方應當與類實現關係更爲密切,爲了隱藏細節,咱們應該考慮將它們寫在@implementation裏。好消息是,在LLVM3.0中,不管是否開啓ARC,編譯器是支持將實例變量寫到實現文件中的。甚至若是沒有特殊須要又用了property,咱們都不該該寫無心義的實例變量申明,由於在@synthesize中進行綁定時,咱們就能夠設置變量名字了,這樣寫的話可讓代碼更加簡潔。

在這裏咱們對着兩個實例變量不須要property(外部成員不該當能訪問到它們),所以咱們把申明移到.m裏中。修改後的.h是這樣的,十分簡潔一看就懂~

1
2
3
4
5
#import  @interface MainViewController : UIViewController @property (nonatomic, retain) IBOutlet UITableView *tableView;   @property (nonatomic, retain) IBOutlet UISearchBar *searchBar;  @end 

而後.m的開頭變成這樣:

1
2
3
4
5
@implementation MainViewController {  NSOperationQueue *queue;   NSMutableString *currentStringValue;  } 

這樣的寫法讓代碼至關靈活,並且不得不認可.m確實是這些實例變量的應該在的地方…build一下,沒問題..固然對於SoundEffect類也能夠作類似的操做,這會讓使用你的類的人很開心,由於.h越簡單越好..P.S.另一個好處能夠減小.h裏的引用,減小編譯時間(雖然不明顯=。=)

而後就能夠在MainViewController裏啓用ARC了,方法很簡單,刪掉Build Phases裏相關文件的-fno-objc-arc標記就能夠了~而後..而後固然是一大堆錯誤啦。咱們來手動一個個改吧,雖然談不上樂趣,可是成功之後也會頗有成就~(若是你不幸在啓用ARC後build仍是成功了,恭喜你遇到了Xcode的bug,請Cmd+Q而後從新打開Xcode把=_=)

dealloc

紅色最密集的地方是dealloc,由於每一行都是release。因爲在這裏dealloc並無作除了releasesuper dealloc以外的任何事情,所以簡單地把整個方法刪掉就行了。固然,在對象被銷燬時,dealloc仍是會被調用的,所以咱們在須要對非ARC管理的內存進行管理和必要的邏輯操做的時候,仍是應該保留dealloc的,固然這涉及到CF以及如下層的東西:好比對於retain的CF對象要CFRelease(),對於malloc()到堆上的東西要free()掉,對於添加的observer能夠在這裏remove,schedule的timer在這裏invalidate等等~[super dealloc]這個消息也再也不須要發了,ARC會自動幫你搞定。

另外,在MRC時代一個常作的事情是在dealloc裏把指向本身的delegate設成nil(不然就等着EXC_BAD_ACCESS吧 :P),而如今通常delegate都是weak的,所以在self被銷燬後這個指針自動被置成nil了,你不用再爲之擔憂,好棒啊..

去掉各類release和autorelease

這個很直接,沒有任何問題。去掉就好了~再也不多說

討論一下Property

在MainViewController.m裏的類擴展中定義了兩個property:

1
2
3
4
@interface MainViewController () @property (nonatomic, retain) NSMutableArray *searchResults; @property (nonatomic, retain) SoundEffect *soundEffect;  @end 

申明的類型是retain,關於retain,assigncopy的討論已經爛大街了,在此再也不討論。在MRC的年代使用property能夠幫助咱們使用dot notation的時候簡化對象的retaincopy,而在ARC時代,這就顯得比較多餘了。在我看來,使用property和點方法來調用setter和getter是沒必要要的。property只在將須要的數據在.h中暴露給其餘類時才須要,而在本類中,只須要用實例變量就能夠。(更新,如今筆者在這點上已經不糾結了,隨意就好,本身明白就行。可是也許仍是用點方法會好一些,至少能夠分清楚究竟是操做了實例變量仍是調用了setter和getter)。所以咱們能夠移去searchResults和soundEffect的@property和@synthesize,並將起移到實例變量申明中:

1
2
3
4
5
6
7
#import "plementation MainViewController {   NSOperationQueue *queue;   NSMutableString *currentStringValue;  NSMutableArray *searchResults;  SoundEffect *soundEffect;  } 

相應地,咱們須要將對應的self.searchResultself.soundEffect的self.都去去掉。在這裏須要注意的是,雖然咱們去掉了soundEffect的property和synthesize,可是咱們依然有一個lazy loading的方法-(SoundEffect *)soundEffect,神奇之處在於(可能你之前也不知道),點方法並不須要@property關鍵字的支持,雖然大部分時間是這麼用的..(property只是對setter或者getter的申明,而點方法是對其的調用,在這個例子的實現中咱們事實上實現了-soundEffect這個getter方法,因此點方法在等號右邊的getter調用是沒有問題的)。爲了不誤解,建議把self.soundEffect的getter調用改寫成[self soundEffect]。

而後咱們看看.h裏的property~裏面有兩個retain的IBOutlet。retain關鍵字在ARC中是依舊可用的,它在ARC中所扮演的角色和strong徹底同樣。爲了不迷惑,最好在須要的時候將其寫爲strong,那樣更符合ARC的規則。對於這兩個property,咱們將其申明爲weak(事實上,若是沒有特別意外,除了最頂層的IBOutlet意外,本身寫的outlet都應該是weak)。經過加載xib獲得的用戶界面,在其從xib文件加載時,就已是view hierarchy的一部分了,而view hierarchy中的指向都是strong的。所以outlet所指向的UI對象不該當再被hold一次了。將這些outlet寫爲weak的最顯而易見的好處是你就不用再viewDidUnload方法中再將這些outlet設爲nil了(不然就算view被摧毀了,可是因爲這些UI對象還在被outlet指針指向而沒法釋放,代碼簡潔了不少啊..)。

在咱們的demo中將IBOutlet的property改成weak而且刪掉viewDidUnload中關於這兩個IBOutlet的內容~

總結一下新加入的property的關鍵字類型:

  • strong 和原來的retain比較類似,strong的property將對應__strong的指針,它將持有所指向的對象
  • weak 不持有所指向的對象,並且當所指對象銷燬時能將本身置爲nil,基本全部的outlet都應該用weak
  • unsafe_unretained 這就是原來的assign。當須要支持iOS4時須要用到這個關鍵字
  • copy 和原來基本同樣..copy一個對象而且爲其建立一個strong指針
  • assign 對於對象來講應該永遠不用assign了,實在須要的話應該用unsafe_unretained代替(基本找不到這種時候,大部分assign應該都被weak替代)。可是對於基本類型好比int,float,BOOL這樣的東西,仍是要用assign。

特別地,對於NSString對象,在MRC時代不少人喜歡用copy,而ARC時代通常喜歡用strong…(我也不懂爲何..求指教)

自由橋接的細節

MainViewController如今剩下的問題都是橋接轉換問題了~有關橋接的部分有三處:

  • (NSString *)CFURLCreateStringByAddingPercentEscapes(…):CFStringRef至NSString *
  • (CFStringRef)text:NSString *至CFStringRef
  • (CFStringRef)@「!‘();:@&=+$,/?%#[]」:NSString 至CFStringRef

編譯器對前兩個進行了報錯,最後一個是常量轉換不涉及內存管理。

關於toll-free bridged,若是不進行細究,NSStringCFStringRef是同樣的東西,新建一個CFStringRef能夠這麼作:

1
CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!",name];

而後,這裏alloc了而s1是一個CF指針,要釋放的話,須要這樣:

1
CFRelease(s1);

類似地能夠用CFStringRef來轉成一個NSString對象(MRC):

1
2
3
4
5
CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault,bytes, kCFStringEncodingMacRoman);  NSString *s3 = (NSString *)s2; // release the object when you're done  [s3 release]; 

在ARC中,編譯器須要知道這些指針應該由誰來負責釋放,若是把一個NSObject看作是CF對象的話,那麼ARC就再也不負責它的釋放工做(記住ARC是only for NSObject的)。對於不須要改變持有者的對象,直接用簡單的bridge就能夠了,好比以前在SoundEffect.m作的轉換。在這裏對於(CFStringRef)text這個轉換,ARC已經負責了text這個NSObject的內存管理,所以這裏咱們須要一個簡單的bridge。而對於CFURLCreateStringByAddingPercentEscapes方法,方法中的create暗示了這個方法將造成一個新的對象,若是咱們不須要NSString轉換,那麼爲了不內存的問題,咱們須要使用CFRelease來釋放它。而這裏咱們須要一個NSString,所以咱們須要告訴編譯器接手它的內存管理工做。這裏咱們使用bridge_transfer關鍵字,將內存管理權由CF object移交給NSObject(或者說ARC)。若是這裏咱們只用bridge的話,內存管理的負責人沒有改變,那麼這裏就會出現一個內存泄露。另外有時候會看到CFBridgingRelease(),這其實就是transfer cast的內聯寫法..是同樣的東西。總之,須要記住的原則是,當在涉及CF層的東西時,若是函數名中有含有Create, Copy, 或者Retain之一,就表示返回的對象的retainCount+1了,對於這樣的對象,最安全的作法是將其放在CFBridgingRelease()裏,來平衡retainrelease

還有一種bridge方式,__bridge_retained。顧名思義,這種轉換將在轉換時將retainCount加1。和CFBridgingRelease()類似,也有一個內聯方法CFBridgingRetain()來負責和CFRelease()進行平衡。

須要注意的是,並不是全部的CF對象都是自由橋接的,好比Core Graphics中的全部對象都不是自由橋接的(如CGImageUIImageCGColorUIColor)。另外也不是隻有自由橋接對象才能用bridge來橋接,一個很好的特例是void (指向任意對象的指針,相似id),對於void 和任意對象的轉換,通常使用_bridge。(這在將ARC運用在Cocos2D中頗有用)

終於搞定了

至此整個工程都ARC了~對於AFHTTPRequestOperation這樣的不支持ARC的第三方代碼,咱們的選擇通常都是就不使用ARC了(或者等開源社區的大大們更新ARC適配版本)。能夠預見,在近期會有愈來愈多的代碼轉向ARC,可是也必定會有大量的代碼暫時或者永遠保持MRC等個,因此對於這些代碼就不用太糾結了~


寫在最後

寫了那麼多,但願你如今能對ARC有個比較全面的瞭解和認識了。ARC確定是之後的趨勢,也確實能讓代碼量大大下降,減小了不少無心義的重複工做,還提升了app的穩定性。可是凡事仍是紙上得來終覺淺,但願做爲開發者的你,在下一個工程中去嘗試用用ARC~相信你會和我同樣,立刻愛上這種make life easier的方式的~

相關文章
相關標籤/搜索