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


這往往是引用的問題。

ARC要求完整的前向引用,也就是說在MRC時代可能僅僅須要在.h中申明@class就能夠,可是在ARC中假設調用某個子類中未覆蓋的父類中的方法的話。必須對父類.h引用,否則無法編譯。

有一篇文章講的非常具體

本文部分實例取自iOS 5 Toturail一書中關於ARC的教程和公開內容。僅用於技術交流和討論。

請不要將本文的部分或所有內容用於商用,謝謝合作。

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

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

寫在開頭

盡管距離WWDC2011和iOS 5已經快一年時間。可是非常多開發人員並沒有利用新方法來提高自己的水平,這點在ARC的使用上非常明顯(特別是國內。基本非常少見到同行轉向ARC)。我以前詢問過一些同行為什么不轉向使用ARC,非常多人的回答是操心內存管理不受自己控制..事實上我個人覺得這是對於ARC機制了解不足從而不自信。所導致的對新事物的恐懼。而作為最須要“追趕時髦”的職業,這種心態將相當不利。謹以此文希望能清楚表述ARC的機理和使用方法。也希望可以成為如今中文入門教學缺失的補充。


什么是ARC

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

在project中使用ARC很easy:僅僅須要像往常那樣編寫代碼,僅僅只是永遠不寫retain,releaseautorelease三個keyword就好~這是ARC的基本原則。當ARC開啟時,編譯器將自己主動在代碼合適的地方插入retainreleaseautorelease,而作為開發人員,全然不須要操心編譯器會做錯(除非開發人員自己錯用ARC了)。好了,ARC相當簡單吧~到此為止,本教程結束。

等等…或許還有其它問題,最嚴重的問題是“我怎么確定讓ARC來管理不會出問題?”或者“用ARC會讓程序性能下降吧”。對於ARC不能正處理內存管理的質疑自從ARC出生以來就一直存在。而如今越來越多的代碼轉向ARC並取得了非常好的效果,這證明了ARC是一套有效的簡化開發復雜程度的機制。另外通過研究ARC的原理,能夠知道使用ARC甚至能提高程序的效率。在接下來將詳解ARC的執行機理而且提供了一個step-by-step的教程,將非ARC的程序轉換為ARC。


ARC工作原理

手動內存管理的機理大家應該已經很清楚了,簡單來說。僅僅要遵循下面三點就能夠在手動內存管理中避免絕大部分的麻煩:

假設須要持有一個對象,那么對其發送retain 假設之后不再使用該對象,那么須要對其發送release(或者autorealse) 每一次對retain,alloc或者new的調用。須要相應一次release或autorealse調用

剛開始學習的人可能只不過知道這些規則,可是在實際使用時難免犯錯。可是當開發人員常常使用手動引用計數 Manual Referecen Counting(MRC)的話,這些規則將逐漸變為本能。你會發現少一個release的代碼怎么看怎么別扭,從而降低或者杜絕內存管理的錯誤。

能夠說MRC的規則很簡單,可是同一時候也很easy出錯。

往往很小的錯誤就將引起crash或者OOM之類的嚴重問題。

在MRC的年代里,為了避免不小心忘寫release,Xcode提供了一個非常有用的小工具來幫助可能存在的代碼問題(Xcode3里默認快捷鍵Shift+A?不記得了),能夠指出潛在的內存泄露或者過多釋放。

而ARC在此基礎上更進一步:ARC是Objective-C編譯器的特性。而不是執行時特性或者垃圾回收機制,ARC所做的僅僅只是是在代碼編譯時為你自己主動在合適的位置插入releaseautorelease,就如同之前MRC時你所做的那樣。因此。至少在效率上ARC機制是不會比MRC弱的,而由於能夠在最合適的地方完畢引用計數的維護,以及部分優化。使用ARC甚至能比MRC取得更高的執行效率。

ARC機制

學習ARC非常easy,在MRC時代你須要自己retain一個想要保持的對象,而如今不須要了。

如今唯一要做的是用一個指針指向這個對象,僅僅要指針沒有被置空。對象就會一直保持在堆上。當將指針指向新值時,原來的對象會被release一次。

這對實例變量,synthesize的變量或者局部變量都是適用的。比方

NSString *firstName = self.textField.text;  

firstName如今指向NSString對象,這時這個對象(textField的內容字符串)將被hold住。比方用字符串@“OneV"作為樣例(盡管實際上不應該用字符串舉樣例。由於字符串的retainCount規則事實上和普通的對象不一樣,大家就把它當作一個普通的對象來看吧…)。這個時候firstName持有了@"OneV"。

一個strong指針

當然,一個對象能夠擁有不止一個的持有者(這個類似MRC中的retainCount>1的情況)。在這個樣例中顯然self.textField.text也是@“OneV",那么如今有兩個指針指向對象@"OneV”(被持有兩次。retainCount=2。事實上對NSString對象說retainCount是有問題的,只是anyway~就這個意思而已.)。

兩個strong指向同一個對象

過了一會兒。或許用戶在textField里輸入了其它的東西,那么self.textField.text指針顯然如今指向了別的字符串,比方@“onevcat",可是這時候原來的對象已然是存在的。由於另一個指針firstName持有它。如今指針的指向關系是這種:

當中一個strong指向了還有一個對象

僅僅有當firstName也被設定了新的值。或者是超出了作用范圍的空間(比方它是局部變量可是這種方法運行完了或者它是實例變量可是這個實例被銷毀了),那么此時firstName也不再持有@“OneV",此時不再有指針指向@"OneV",在ARC下這樣的狀況發生后對象@"OneV"即被銷毀,內存釋放。

沒有strong指向@

類似於firstNameself.textField.text這種指針使用keywordstrong進行標志,它意味着僅僅要該指針指向某個對象,那么這個對象就不會被銷毀。反過來說,ARC的一個基本規則即是。僅僅要某個對象被任一strong指針指向。那么它將不會被銷毀。

假設對象沒有被不論什么strong指針指向。那么就將被銷毀。

在默認情況下,全部的實例變量和局部變量都是strong類型的。

能夠說strong類型的指針在行為上和MRC時代retain的property是比較相似的。

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

比方:

__weak NSString *weakName = self.textField.text  

得到的指向關系是:

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

這里聲明了一個weak的指針weakName。它並不持有@“onevcat"。

假設self.textField.text的內容發生改變的話。依據之前提到的"僅僅要某個對象被任一strong指針指向,那么它將不會被銷毀。假設對象沒有被不論什么strong指針指向。那么就將被銷毀”原則。此時指向@“onevcat"的指針中沒有strong類型的指針,@"onevcat"將被銷毀。同一時候,在ARC機制作用下,全部指向這個對象的weak指針將被置為nil。這個特性相當實用,相信無數的開發人員都以前被指針指向已釋放對象所造成的EXCBADACCESS困擾過,使用ARC以后,不論是strong還是weak類型的指針。都不再會指向一個dealloced的對象,從根源上攻克了意外釋放導致的crash

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設計模式

注意類似以下的代碼似乎是沒有什么意義的:

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

由於strweak。它不會持有alloc出來的NSString對象。因此這個對象由於沒有有效的strong指針指向,所以在生成的同一時候就被銷毀了。假設我們在Xcode中寫了上面的代碼。我們應該會得到一個警告,由於不管何時這樣的情況似乎都是不太可能出現的。你能夠把weak換成strong來消除警告。或者直接前面什么都不寫,由於ARC中默認的指針類型就是strong

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

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

ARC能夠為開發人員節省非常多代碼,使用ARC以后再也不須要關心什么時候retain。什么時候release,可是這並不意味你能夠不思考內存管理,你可能須要常常性地問自己這個問題:誰持有這個對象?

比方以下的代碼,如果array是一個NSMutableArray而且里面至少有一個對象:

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

在MRC時代這幾行代碼應該就掛掉了,由於array中0號對象被remove以后就被馬上銷毀了,因此obj指向了一個dealloced的對象。因此在NSLog的時候將出現EXCBADACCESS。而在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

樣例非常easy,這是一個查找歌手的應用,包括一個簡單的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包的目的一方面是為了資源easy管理,還有一方面也是主要方面時為了不和其它資源發生沖突(Xcode中資源名字是資源的唯一標識,同名字的資源僅僅能出現一次。而放到bundle包里能夠避免這個潛在的問題)。

SVProgresHUD能夠在這里找到https://github.com/samvermette/SVProgressHUD

高速過一遍這個應用吧:MainViewControllerUIViewController的子類,相應的xib文件定義了相應的UITableViewUISearchBar

TableView中顯示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,releaseautoreleasekeyword去掉,在之前我們明白幾件事情:

  • Xcode提供了一個ARC自己主動轉換工具。能夠幫助你將源代碼轉為ARC
  • 當然你也能夠自己動手完畢ARC轉換
  • 同一時候你也能夠指定對於某些你不想轉換的代碼禁用ARC,這對於非常多龐大復雜的還沒有轉至ARC的第三方庫幫助非常大。由於不是你寫的代碼你想動手改動的話代碼超級easymess…

對於我們的demo。為了說明問題,這三種策略我們都將採用,注意這只不過為了展示怎樣轉換。

實際操作中不須要這么麻煩,並且今后的絕大部分情況應該是從project建立開始就是ARC的。

選擇LLVM compiler 3.0

首先,ARC是LLVM3.0編譯器的特性,而老的project特別是Xcode3時代的project的默認編譯器非常可能是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一下試試看,經過改動后的demoproject沒有不論什么警告和錯誤,這是非常好的開始。

(對於存在警告的代碼,這里是非常好的修復的時機..請在轉換前確保原來的代碼沒有內存問題)。

打開ARC

接下來就是完畢從MRC到ARC的偉大轉換了。還是在Build Settings頁面,把Objective-C Automatic Reference Counting改成YES(假設找不到的話請看一看搜索欄前面的小標簽是不是調成All了..這個選項在Basic里是不出現的)。這樣我們的project就將在全部源碼中啟用ARC了。

然后…試着編譯一下看看,嗯..無數的錯誤。

請耐心聆聽編譯器的傾訴。由於非常多時候它是你唯一的伙伴

這是非常正常的,由於ARC里不同意出現retain,release之類的,而MRC的代碼這些是肯定會有的東西。

我們能夠手動一個一個相應地去修復這些錯誤,可是這非常麻煩。Xcode為我們提供了一個自己主動轉換工具。能夠幫助重寫源碼。簡單來說就是去掉多余的語句而且重寫一些propertykeyword。

使用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里:

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,bridgetransfer和bridgeretained三個keyword。

關於選取哪個keyword做轉換,須要由實際的代碼行為來決定。假設對於自由橋接機制感興趣,大家能夠自己找找的相關內容。比方適用類型內部機制一個簡單介紹~之后我也會對這個問題做進一步說明

回到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多錯的嘛…答案非常easy。在自己主動轉換的時候由於我們沒有勾選這兩個文件,因此編譯器在自己主動轉換過后為這兩個文件標記了"不使用ARC編譯"。

能夠看到在target的Building Phases下,MainViewController.m和AFHTTPRequestOperation.m兩個文件后面被加上了-fno-objc-arc的編譯標記。被加上該標記的文件將不使用ARC規則進行編譯。(相對地,假設你想強制對某幾個文件啟用ARC的話,能夠為其加上-fobjc-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對象了..因此類似以下這種代碼是不可用的

typedef struct {  
    UIImage *selectedImage; 
    UIImage *disabledImage; 
} ButtonImages;

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

手動轉換

剛才做了對demo的大部分轉換。還剩下了MainViewController和AFHTTPRequestOperation是MRC。可是因為使用了-fno-objc-arc,因此如今編譯和執行都沒有問題了。以下我們看看怎樣手動把MainViewController轉為ARC,這也有助於進一步理解ARC的規則。

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

@interface MainViewController : UIViewController  
{ 
    NSOperationQueue *queue;
    NSMutableString *currentStringValue; 
}

我們最好還是細致考慮一下,為什么在interface里出現了實例變量的申明?通常來說,實例變量僅僅是在類的實例中被使用,而你所寫的類的使用者並沒有太多必要了解你的類中有哪些實例變量。而對於絕大部分的實例變量,應該都是protected或者private的,對它們的操作僅僅應該用settergetter。而這正是property所要做的工作。能夠說。將實例變量寫在頭文件里是一種遺留的陋習。更好的寫實例變量名字的地方應當與類實現關系更為密切,為了隱藏細節,我們應該考慮將它們寫在@implementation里。

好消息是。在LLVM3.0中。不論是否開啟ARC,編譯器是支持將實例變量寫到實現文件里的。甚至假設沒有特殊須要又用了property,我們都不應該寫無意義的實例變量申明,由於在@synthesize中進行綁定時,我們就能夠設置變量名字了。這樣寫的話能夠讓代碼更加簡潔。

在這里我們對着兩個實例變量不須要property(外部成員不應當能訪問到它們),因此我們把申明移到.m里中。改動后的.h是這種,十分簡潔一看就懂~

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

然后.m的開頭變成這樣:

@implementation MainViewController 
{ 
    NSOperationQueue *queue;  
    NSMutableString *currentStringValue;  
}

這種寫法讓代碼相當靈活。並且不得不承認.m確實是這些實例變量的應該在的地方…build一下,沒問題..當然對於SoundEffect類也能夠做相似的操作,這會讓使用你的類的人非常開心,由於.h越簡單越好..P.S.另外一個優點能夠降低.h里的引用。降低編譯時間(盡管不明顯=。=)

然后就能夠在MainViewController里啟用ARC了,方法非常easy。刪掉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(否則就等着EXCBADACCESS吧 :P),而如今一般delegate都是weak的。因此在self被銷毀后這個指針自己主動被置成nil了,你不用再為之操心。好棒啊..

去掉各種release和autorelease

這個非常直接。沒有不論什么問題。去掉即可了~不再多說

討論一下Property

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

@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。並將起移到實例變量申明中:

#import "plementation MainViewController
{ 
    NSOperationQueue *queue; 
    NSMutableString *currentStringValue;
    NSMutableArray *searchResults;
    SoundEffect *soundEffect; 
}

相應地,我們須要將相應的self.searchResultself.soundEffect的self.都去去掉。

在這里須要注意的是。盡管我們去掉了soundEffect的property和synthesize,可是我們依舊有一個lazy loading的方法-(SoundEffect *)soundEffect,奇妙之處在於(可能你曾經也不知道)。點方法並不須要@propertykeyword的支持,盡管大部分時間是這么用的..(property僅僅是對setter或者getter的申明,而點方法是對其的調用,在這個樣例的實現中我們其實實現了-soundEffect這個getter方法。所以點方法在等號右邊的getter調用是沒有問題的)。為了避免誤解,建議把self.soundEffect的getter調用改寫成[self soundEffect]。

然后我們看看.h里的property~里面有兩個retain的IBOutlet。retainkeyword在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的keyword類型:

  • strong 和原來的retain比較相似。strong的property將相應__strong的指針。它將持有所指向的對象
  • weak 不持有所指向的對象。並且當所指對象銷毀時能將自己置為nil。基本全部的outlet都應該用weak
  • unsafe_unretained 這就是原來的assign。

    當須要支持iOS4時須要用到這個keyword

  • 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能夠這么做:

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

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

CFRelease(s1);  

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

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_transferkeyword。將內存管理權由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中非常實用)

最終搞定了

至此整個project都ARC了~對於AFHTTPRequestOperation這種不支持ARC的第三方代碼,我們的選擇一般都是就不使用ARC了(或者等開源社區的大大們更新ARC適配版本號)。能夠預見,在最近會有越來越多的代碼轉向ARC,可是也一定會有大量的代碼臨時或者永遠保持MRC等個,所以對於這些代碼就不用太糾結了~



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM