iOS开发ARC入门和使用
本文部分實(shí)例取自iOS 5 Toturail一書中關(guān)于ARC的教程和公開內(nèi)容,僅用于技術(shù)交流和討論。請(qǐng)不要將本文的部分或全部?jī)?nèi)容用于商用,謝謝合作。
歡迎轉(zhuǎn)載本文,但是轉(zhuǎn)載請(qǐng)注明本文出處:http://www.onevcat.com/2012/06/arc-hand-by-hand/
本文適合人群:對(duì)iOS開發(fā)有一定基礎(chǔ),熟悉iOS開發(fā)中內(nèi)存管理的Reference Counting機(jī)制,對(duì)ARC機(jī)制有聽(tīng)聞很向往但是一直由于種種原因沒(méi)有使用的童鞋。本文將從ARC機(jī)理入手對(duì)這個(gè)解放廣大iOS開發(fā)者的偉大機(jī)制進(jìn)行一個(gè)剖析,并逐步引導(dǎo)你開始使用ARC。一旦習(xí)慣ARC,你一定會(huì)被它的簡(jiǎn)潔高效所征服。
寫在開頭
雖然距離WWDC2011和iOS 5已經(jīng)快一年時(shí)間,但是很多開發(fā)者并沒(méi)有利用新方法來(lái)提高自己的水平,這點(diǎn)在ARC的使用上非常明顯(特別是國(guó)內(nèi),基本很少見(jiàn)到同行轉(zhuǎn)向ARC)。我曾經(jīng)詢問(wèn)過(guò)一些同行為什么不轉(zhuǎn)向使用ARC,很多人的回答是擔(dān)心內(nèi)存管理不受自己控制..其實(shí)我個(gè)人認(rèn)為這是對(duì)于ARC機(jī)制了解不足從而不自信,所導(dǎo)致的對(duì)新事物的恐懼。而作為最需要“追趕時(shí)髦”的職業(yè),這樣的心態(tài)將相當(dāng)不利。謹(jǐn)以此文希望能清楚表述ARC的機(jī)理和用法,也希望能夠成為現(xiàn)在中文入門教學(xué)缺失的補(bǔ)充。
什么是ARC
Automatic Reference Counting,自動(dòng)引用計(jì)數(shù),即ARC,可以說(shuō)是WWDC2011和iOS5所引入的最大的變革和最激動(dòng)人心的變化。ARC是新的LLVM 3.0編譯器的一項(xiàng)特性,使用ARC,可以說(shuō)一舉解決了廣大iOS開發(fā)者所憎恨的手動(dòng)內(nèi)存管理的麻煩。
在工程中使用ARC非常簡(jiǎn)單:只需要像往常那樣編寫代碼,只不過(guò)永遠(yuǎn)不寫retain,release和autorelease三個(gè)關(guān)鍵字就好~這是ARC的基本原則。當(dāng)ARC開啟時(shí),編譯器將自動(dòng)在代碼合適的地方插入retain,?release和autorelease,而作為開發(fā)者,完全不需要擔(dān)心編譯器會(huì)做錯(cuò)(除非開發(fā)者自己錯(cuò)用ARC了)。好了,ARC相當(dāng)簡(jiǎn)單吧~到此為止,本教程結(jié)束。
等等…也許還有其他問(wèn)題,最嚴(yán)重的問(wèn)題是“我怎么確定讓ARC來(lái)管理不會(huì)出問(wèn)題?”或者“用ARC會(huì)讓程序性能下降吧”。對(duì)于ARC不能正處理內(nèi)存管理的質(zhì)疑自從ARC出生以來(lái)就一直存在,而現(xiàn)在越來(lái)越多的代碼轉(zhuǎn)向ARC并取得了很好的效果,這證明了ARC是一套有效的簡(jiǎn)化開發(fā)復(fù)雜程度的機(jī)制,另外通過(guò)研究ARC的原理,可以知道使用ARC甚至能提高程序的效率。在接下來(lái)將詳細(xì)解釋ARC的運(yùn)行機(jī)理并且提供了一個(gè)step-by-step的教程,將非ARC的程序轉(zhuǎn)換為ARC。
ARC工作原理
手動(dòng)內(nèi)存管理的機(jī)理大家應(yīng)該已經(jīng)非常清楚了,簡(jiǎn)單來(lái)說(shuō),只要遵循以下三點(diǎn)就可以在手動(dòng)內(nèi)存管理中避免絕大部分的麻煩:
如果需要持有一個(gè)對(duì)象,那么對(duì)其發(fā)送retain 如果之后不再使用該對(duì)象,那么需要對(duì)其發(fā)送release(或者autorealse) 每一次對(duì)retain,alloc或者new的調(diào)用,需要對(duì)應(yīng)一次release或autorealse調(diào)用
初學(xué)者可能僅僅只是知道這些規(guī)則,但是在實(shí)際使用時(shí)難免犯錯(cuò)。但是當(dāng)開發(fā)者經(jīng)常使用手動(dòng)引用計(jì)數(shù) Manual Referecen Counting(MRC)的話,這些規(guī)則將逐漸變?yōu)楸灸堋D銜?huì)發(fā)現(xiàn)少一個(gè)release的代碼怎么看怎么別扭,從而減少或者杜絕內(nèi)存管理的錯(cuò)誤。可以說(shuō)MRC的規(guī)則非常簡(jiǎn)單,但是同時(shí)也非常容易出錯(cuò)。往往很小的錯(cuò)誤就將引起crash或者OOM之類的嚴(yán)重問(wèn)題。
在MRC的年代里,為了避免不小心忘寫release,Xcode提供了一個(gè)很實(shí)用的小工具來(lái)幫助可能存在的代碼問(wèn)題(Xcode3里默認(rèn)快捷鍵Shift+A?不記得了),可以指出潛在的內(nèi)存泄露或者過(guò)多釋放。而ARC在此基礎(chǔ)上更進(jìn)一步:ARC是Objective-C編譯器的特性,而不是運(yùn)行時(shí)特性或者垃圾回收機(jī)制,ARC所做的只不過(guò)是在代碼編譯時(shí)為你自動(dòng)在合適的位置插入release或autorelease,就如同之前MRC時(shí)你所做的那樣。因此,至少在效率上ARC機(jī)制是不會(huì)比MRC弱的,而因?yàn)榭梢栽谧詈线m的地方完成引用計(jì)數(shù)的維護(hù),以及部分優(yōu)化,使用ARC甚至能比MRC取得更高的運(yùn)行效率。
ARC機(jī)制
學(xué)習(xí)ARC很簡(jiǎn)單,在MRC時(shí)代你需要自己retain一個(gè)想要保持的對(duì)象,而現(xiàn)在不需要了。現(xiàn)在唯一要做的是用一個(gè)指針指向這個(gè)對(duì)象,只要指針沒(méi)有被置空,對(duì)象就會(huì)一直保持在堆上。當(dāng)將指針指向新值時(shí),原來(lái)的對(duì)象會(huì)被release一次。這對(duì)實(shí)例變量,synthesize的變量或者局部變量都是適用的。比如
NSString *firstName = self.textField.text;firstName現(xiàn)在指向NSString對(duì)象,這時(shí)這個(gè)對(duì)象(textField的內(nèi)容字符串)將被hold住。比如用字符串@“OneV"作為例子(雖然實(shí)際上不應(yīng)該用字符串舉例子,因?yàn)樽址膔etainCount規(guī)則其實(shí)和普通的對(duì)象不一樣,大家就把它當(dāng)作一個(gè)普通的對(duì)象來(lái)看吧…),這個(gè)時(shí)候firstName持有了@"OneV"。
當(dāng)然,一個(gè)對(duì)象可以擁有不止一個(gè)的持有者(這個(gè)類似MRC中的retainCount>1的情況)。在這個(gè)例子中顯然self.textField.text也是@“OneV",那么現(xiàn)在有兩個(gè)指針指向?qū)ο?#64;"OneV”(被持有兩次,retainCount=2,其實(shí)對(duì)NSString對(duì)象說(shuō)retainCount是有問(wèn)題的,不過(guò)anyway~就這個(gè)意思而已.)。
過(guò)了一會(huì)兒,也許用戶在textField里輸入了其他的東西,那么self.textField.text指針顯然現(xiàn)在指向了別的字符串,比如@“onevcat",但是這時(shí)候原來(lái)的對(duì)象已然是存在的,因?yàn)檫€有一個(gè)指針firstName持有它。現(xiàn)在指針的指向關(guān)系是這樣的:
只有當(dāng)firstName也被設(shè)定了新的值,或者是超出了作用范圍的空間(比如它是局部變量但是這個(gè)方法執(zhí)行完了或者它是實(shí)例變量但是這個(gè)實(shí)例被銷毀了),那么此時(shí)firstName也不再持有@“OneV",此時(shí)不再有指針指向@"OneV",在ARC下這種狀況發(fā)生后對(duì)象@"OneV"即被銷毀,內(nèi)存釋放。
類似于firstName和self.textField.text這樣的指針使用關(guān)鍵字strong進(jìn)行標(biāo)志,它意味著只要該指針指向某個(gè)對(duì)象,那么這個(gè)對(duì)象就不會(huì)被銷毀。反過(guò)來(lái)說(shuō),ARC的一個(gè)基本規(guī)則即是,只要某個(gè)對(duì)象被任一strong指針指向,那么它將不會(huì)被銷毀。如果對(duì)象沒(méi)有被任何strong指針指向,那么就將被銷毀。在默認(rèn)情況下,所有的實(shí)例變量和局部變量都是strong類型的。可以說(shuō)strong類型的指針在行為上和MRC時(shí)代retain的property是比較相似的。
既然有strong,那肯定有weak咯~weak類型的指針也可以指向?qū)ο?#xff0c;但是并不會(huì)持有該對(duì)象。比如:
__weak NSString *weakName = self.textField.text得到的指向關(guān)系是:
這里聲明了一個(gè)weak的指針weakName,它并不持有@“onevcat"。如果self.textField.text的內(nèi)容發(fā)生改變的話,根據(jù)之前提到的"只要某個(gè)對(duì)象被任一strong指針指向,那么它將不會(huì)被銷毀。如果對(duì)象沒(méi)有被任何strong指針指向,那么就將被銷毀”原則,此時(shí)指向@“onevcat"的指針中沒(méi)有strong類型的指針,@"onevcat"將被銷毀。同時(shí),在ARC機(jī)制作用下,所有指向這個(gè)對(duì)象的weak指針將被置為nil。這個(gè)特性相當(dāng)有用,相信無(wú)數(shù)的開發(fā)者都曾經(jīng)被指針指向已釋放對(duì)象所造成的EXCBADACCESS困擾過(guò),使用ARC以后,不論是strong還是weak類型的指針,都不再會(huì)指向一個(gè)dealloced的對(duì)象,從根源上解決了意外釋放導(dǎo)致的crash。
不過(guò)在大部分情況下,weak類型的指針可能并不會(huì)很常用。比較常見(jiàn)的用法是在兩個(gè)對(duì)象間存在包含關(guān)系時(shí):對(duì)象1有一個(gè)strong指針指向?qū)ο?,并持有它,而對(duì)象2中只有一個(gè)weak指針指回對(duì)象1,從而避免了循環(huán)持有。一個(gè)常見(jiàn)的例子就是oc中常見(jiàn)的delegate設(shè)計(jì)模式,viewController中有一個(gè)strong指針指向它所負(fù)責(zé)管理的UITableView,而UITableView中的dataSource和delegate指針都是指向viewController的weak指針。可以說(shuō),weak指針的行為和MRC時(shí)代的assign有一些相似點(diǎn),但是考慮到weak指針更聰明些(會(huì)自動(dòng)指向nil),因此還是有所不同的。細(xì)節(jié)的東西我們稍后再說(shuō)。
注意類似下面的代碼似乎是沒(méi)有什么意義的:
__weak NSString *str = [[NSString alloc] initWithFormat:…]; NSLog(@"%@",str); //輸出是"(null)"由于str是weak,它不會(huì)持有alloc出來(lái)的NSString對(duì)象,因此這個(gè)對(duì)象由于沒(méi)有有效的strong指針指向,所以在生成的同時(shí)就被銷毀了。如果我們?cè)赬code中寫了上面的代碼,我們應(yīng)該會(huì)得到一個(gè)警告,因?yàn)闊o(wú)論何時(shí)這種情況似乎都是不太可能出現(xiàn)的。你可以把weak換成strong來(lái)消除警告,或者直接前面什么都不寫,因?yàn)锳RC中默認(rèn)的指針類型就是strong。
property也可以用strong或weak來(lái)標(biāo)記,簡(jiǎn)單地把原來(lái)寫retain和assign的地方替換成strong或者weak就可以了。
@property (nonatomic, strong) NSString *firstName; @property (nonatomic, weak) id delegate;ARC可以為開發(fā)者節(jié)省很多代碼,使用ARC以后再也不需要關(guān)心什么時(shí)候retain,什么時(shí)候release,但是這并不意味你可以不思考內(nèi)存管理,你可能需要經(jīng)常性地問(wèn)自己這個(gè)問(wèn)題:誰(shuí)持有這個(gè)對(duì)象?
比如下面的代碼,假設(shè)array是一個(gè)NSMutableArray并且里面至少有一個(gè)對(duì)象:
id obj = [array objectAtIndex:0]; [array removeObjectAtIndex:0];? NSLog(@"%@",obj);在MRC時(shí)代這幾行代碼應(yīng)該就掛掉了,因?yàn)閍rray中0號(hào)對(duì)象被remove以后就被立即銷毀了,因此obj指向了一個(gè)dealloced的對(duì)象,因此在NSLog的時(shí)候?qū)⒊霈F(xiàn)EXCBADACCESS。而在ARC中由于obj是strong的,因此它持有了array中的首個(gè)對(duì)象,array不再是該對(duì)象的唯一持有者。即使我們從array中將obj移除了,它也依然被別的指針持有,因此不會(huì)被銷毀。
一點(diǎn)提醒
ARC也有一些缺點(diǎn),對(duì)于初學(xué)者來(lái)說(shuō),可能僅只能將ARC用在objective-c對(duì)象上(也即繼承自NSObject的對(duì)象),但是如果涉及到較為底層的東西,比如Core Foundation中的malloc()或者free()等,ARC就鞭長(zhǎng)莫及了,這時(shí)候還是需要自己手動(dòng)進(jìn)行內(nèi)存管理。在之后我們會(huì)看到一些這方面的例子。另外為了確保ARC能正確的工作,有些語(yǔ)法規(guī)則也會(huì)因?yàn)锳RC而變得稍微嚴(yán)格一些。
ARC確實(shí)可以在適當(dāng)?shù)牡胤綖榇a添加retain或者release,但是這并不意味著你可以完全忘記內(nèi)存管理,在一些情況下還是需要在合適的地方把strong指針手動(dòng)設(shè)置到nil,否則app很可能會(huì)oom。簡(jiǎn)單說(shuō)還是那句話,你必須時(shí)刻清醒誰(shuí)持有了哪些對(duì)象,而這些持有者在什么時(shí)候應(yīng)該變?yōu)橹赶騨il。
ARC必然是Objective-C以及Apple開發(fā)的趨勢(shì),今后也會(huì)有越來(lái)越多的項(xiàng)目采用ARC(甚至不排除MRC在未來(lái)某個(gè)版本被棄用的可能),Apple也一直鼓勵(lì)開發(fā)者開始使用ARC,因?yàn)樗_實(shí)可以簡(jiǎn)化代碼并增強(qiáng)其穩(wěn)定性。可以這么說(shuō),使用ARC之后,由于內(nèi)存問(wèn)題造成的crash基本就是過(guò)去式了(OOM除外 :P)
我們正處于由MRC向ARC轉(zhuǎn)變的節(jié)點(diǎn)上,因此可能有時(shí)候我們需要在ARC和MRC的代碼間來(lái)回切換和適配。Apple也想到了這一點(diǎn),因此為開發(fā)這提供了一些ARC和非ARC代碼混編的機(jī)制,這些也將在之后的例子中列出。另外ARC甚至可以用在C++的代碼中,而通過(guò)遵守一些代碼規(guī)則,iOS 4里也可以使用ARC(雖然我個(gè)人認(rèn)為在現(xiàn)在iOS 6都呼之欲出的年代已經(jīng)基本沒(méi)有需要為iOS 4做適配的必要了)、
總之,聰明的開發(fā)者總會(huì)嘗試盡可能的自動(dòng)化流程,已減輕自己的工作負(fù)擔(dān),而ARC恰恰就為我們提供了這樣的好處:自動(dòng)幫我們完成了很多以前需要手動(dòng)完成的工作,因此對(duì)我來(lái)說(shuō),轉(zhuǎn)向ARC是一件不需要考慮的事情。
具體操作
說(shuō)了這么多,終于可以實(shí)踐一下了。在決定使用ARC后,很多開發(fā)者面臨的首要問(wèn)題是不知如何下手。因?yàn)榭赡苁稚系捻?xiàng)目已經(jīng)用MRC寫了一部分,不想麻煩做轉(zhuǎn)變;或者因?yàn)樾马?xiàng)目里用ARC時(shí)遇到了奇怪的問(wèn)題,從而放棄ARC退回MRC。這都是常見(jiàn)的問(wèn)題,而在下面,將通過(guò)一個(gè)demo引導(dǎo)大家徹底轉(zhuǎn)向ARC的世界。
Demo
例子很簡(jiǎn)單,這是一個(gè)查找歌手的應(yīng)用,包含一個(gè)簡(jiǎn)單的UITableView和一個(gè)搜索框,當(dāng)用戶在搜索框搜索時(shí),調(diào)用MusicBrainz的API完成名字搜索和匹配。MusicBrainz是一個(gè)開放的音樂(lè)信息平臺(tái),它提供了一個(gè)免費(fèi)的XML網(wǎng)頁(yè)服務(wù),如果對(duì)MusicBrainz比較有興趣的話,可以到它的官網(wǎng)逛一逛。
Demo的起始例子可以從這里下載,為了照顧新人,在這邊進(jìn)行簡(jiǎn)單說(shuō)明。在Xcode中打開下載的例子,應(yīng)該可以看到如下內(nèi)容(Xcode和iOS開發(fā)熟練者請(qǐng)?zhí)^(guò)此段)
AppDelegate.h/m 這是整個(gè)app的delegate,沒(méi)什么特殊的,每個(gè)iOS/Mac程序在main函數(shù)以后的入口,由此進(jìn)入app的生命周期。在這里加載了最初的viewController并將其放到Window中展示出來(lái)。另外appDelegate還負(fù)責(zé)處理程序開始退出等系統(tǒng)委托的事件
MainViewController.h/m/xib 這個(gè)demo最主要的ViewController,含有一個(gè)TableView和一個(gè)搜索條。 SoundEffect.h/m 簡(jiǎn)單的播放聲音的類,在MusicBrainz搜索完畢時(shí)播放一個(gè)音效。 main.m 程序入口,所有c程序都從main函數(shù)開始執(zhí)行
AFHTTPRequestOperation.h/m 這是有名的網(wǎng)絡(luò)框架AFNetworking的一部分,用來(lái)幫助等簡(jiǎn)單地處理web服務(wù)請(qǐng)求。這里只包含了這一個(gè)類而沒(méi)有將全部的AFNetworking包括進(jìn)來(lái),因?yàn)槲覀冎挥昧诉@一個(gè)類。完整的框架代碼可以在github的相關(guān)頁(yè)面上找到https://github.com/gowalla/AFNetworking
SVProgresHUD.h/m/bundle 是一個(gè)常用的進(jìn)度條指示,當(dāng)搜索的時(shí)候出現(xiàn)以提示用戶正在搜索請(qǐng)稍后。bundle是資源包,里面包含了幾張?jiān)擃愑玫降膱D片,打進(jìn)bundle包的目的一方面是為了資源容易管理,另一方面也是主要方面時(shí)為了不和其他資源發(fā)生沖突(Xcode中資源名字是資源的唯一標(biāo)識(shí),同名字的資源只能出現(xiàn)一次,而放到bundle包里可以避免這個(gè)潛在的問(wèn)題)。SVProgresHUD可以在這里找到https://github.com/samvermette/SVProgressHUD
快速過(guò)一遍這個(gè)應(yīng)用吧:MainViewController是UIViewController的子類,對(duì)應(yīng)的xib文件定義了對(duì)應(yīng)的UITableView和UISearchBar。TableView中顯示searchResult數(shù)組中的內(nèi)容。當(dāng)用戶搜索時(shí),用AFHTTPRequestOperation發(fā)一個(gè)HTTP請(qǐng)求,當(dāng)從MusicBrainz得到回應(yīng)后將結(jié)果放入searchResult數(shù)組中并用tableView顯示,當(dāng)返回結(jié)果是空時(shí)在tableView中顯示沒(méi)找到。主要的邏輯都在MainViewController.m中的-searchBarSearchButtonClicked:方法中,生成了用于查詢的URL,根據(jù)MusicBrainz的需求替換了請(qǐng)求的header,并且完成了返回邏輯,然后在主線程中刷新UI。整個(gè)程序還是比較簡(jiǎn)單的~
MRC到ARC的自動(dòng)轉(zhuǎn)換
回到正題,我們討論的是ARC,關(guān)于REST API和XML解析的技術(shù)細(xì)節(jié)就暫時(shí)先忽略吧..整個(gè)程序都是用MRC來(lái)進(jìn)行內(nèi)存管理的,首先來(lái)讓我們把這個(gè)demo轉(zhuǎn)成ARC吧。基本上轉(zhuǎn)換為ARC意味著把所有的retain,release和autorelease關(guān)鍵字去掉,在之前我們明確幾件事情:
- Xcode提供了一個(gè)ARC自動(dòng)轉(zhuǎn)換工具,可以幫助你將源碼轉(zhuǎn)為ARC?
- 當(dāng)然你也可以自己動(dòng)手完成ARC轉(zhuǎn)換?
- 同時(shí)你也可以指定對(duì)于某些你不想轉(zhuǎn)換的代碼禁用ARC,這對(duì)于很多龐大復(fù)雜的還沒(méi)有轉(zhuǎn)至ARC的第三方庫(kù)幫助很大,因?yàn)椴皇悄銓懙拇a你想動(dòng)手修改的話代碼超級(jí)容易mess…
對(duì)于我們的demo,為了說(shuō)明問(wèn)題,這三種策略我們都將采用,注意這僅僅只是為了展示如何轉(zhuǎn)換。實(shí)際操作中不需要這么麻煩,而且今后的絕大部分情況應(yīng)該是從工程建立開始就是ARC的。
首先,ARC是LLVM3.0編譯器的特性,而老的工程特別是Xcode3時(shí)代的工程的默認(rèn)編譯器很可能是GCC或者LLVM-GCC,因此第一步就是確認(rèn)編譯器是否正確。在Project設(shè)置面板,選擇target,在Build Settings中將Compiler for C/C++/Objective-C選為Apple LLVM compiler 3.0或以上。為了確保之后轉(zhuǎn)換的順利,在這里我個(gè)人建議最好把Treat Warnings as Errors和 Run Static Analyzer都打開,確保在改變編譯器后代碼依舊沒(méi)有警告或者內(nèi)存問(wèn)題(雖然靜態(tài)分析可能不太能保證這一點(diǎn),但是聊勝于無(wú))。好了~clean(Shift+Cmd+K)以后Bulid一下試試看,經(jīng)過(guò)修改后的demo工程沒(méi)有任何警告和錯(cuò)誤,這是很好的開始。(對(duì)于存在警告的代碼,這里是很好的修復(fù)的時(shí)機(jī)..請(qǐng)?jiān)谵D(zhuǎn)換前確保原來(lái)的代碼沒(méi)有內(nèi)存問(wèn)題)。
接下來(lái)就是完成從MRC到ARC的偉大轉(zhuǎn)換了。還是在Build Settings頁(yè)面,把Objective-C Automatic Reference Counting改成YES(如果找不到的話請(qǐng)看一看搜索欄前面的小標(biāo)簽是不是調(diào)成All了..這個(gè)選項(xiàng)在Basic里是不出現(xiàn)的),這樣我們的工程就將在所有源代碼中啟用ARC了。然后…試著編譯一下看看,嗯..無(wú)數(shù)的錯(cuò)誤。
這是很正常的,因?yàn)锳RC里不允許出現(xiàn)retain,release之類的,而MRC的代碼這些是肯定會(huì)有的東西。我們可以手動(dòng)一個(gè)一個(gè)對(duì)應(yīng)地去修復(fù)這些錯(cuò)誤,但是這很麻煩。Xcode為我們提供了一個(gè)自動(dòng)轉(zhuǎn)換工具,可以幫助重寫源代碼,簡(jiǎn)單來(lái)說(shuō)就是去掉多余的語(yǔ)句并且重寫一些property關(guān)鍵字。
這個(gè)小工具是Edit->Refactor下的Convert to Objective-C ARC,點(diǎn)擊后會(huì)讓我們選擇要轉(zhuǎn)換哪幾個(gè)文件,在這里為了說(shuō)明除了自動(dòng)轉(zhuǎn)換外的方法,我們不全部轉(zhuǎn)換,而只是選取其中幾個(gè)轉(zhuǎn)換(MainViewController.m和AFHTTPRequestOperation.m不做轉(zhuǎn)換,之后我們?cè)偈謩?dòng)將這兩個(gè)轉(zhuǎn)為ARC)。注意到這個(gè)對(duì)話框上有個(gè)警告標(biāo)志告訴我們target已經(jīng)是ARC了,這是由于之前我們?cè)贐uild Settings里已經(jīng)設(shè)置了啟用ARC,其實(shí)直接在這里做轉(zhuǎn)換后Xcode會(huì)自動(dòng)幫我們開啟ARC。點(diǎn)擊檢查后,Xcode告訴我們一個(gè)不幸的消息,不能轉(zhuǎn)換,需要修復(fù)ARC readiness issues..后面還告訴我們要看到所有的所謂的ARC readiness issues,可以到設(shè)置的General里把Continue building after errors勾上…What the f**k…好吧~先乖乖聽(tīng)從Xcode的建議"Cmd+,“然后Continue building after errors打勾然后再build。
問(wèn)題依舊,不過(guò)在issue面板里應(yīng)該可以看到所有出問(wèn)題的代碼了。在我們的例子里,問(wèn)題出在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;} }這里代碼嘗試把一個(gè)NSURL指針強(qiáng)制轉(zhuǎn)換為一個(gè)CFURLRef指針。這里涉及到一些Core Services特別是Core Foundation(CF)的東西,AudioServicesCreateSystemSoundID()函數(shù)接受CFURLRef為參數(shù),這是一個(gè)CF的概念,但是我們?cè)谳^高的抽象層級(jí)上所建立的是NSURL對(duì)象。在Cocoa框架中,有很多頂層對(duì)象對(duì)底層的抽象,而在使用中我們往往可以不加區(qū)別地對(duì)這兩種對(duì)象進(jìn)行同樣的對(duì)待,這類對(duì)象即為可以"自由橋接"的對(duì)象(toll-free bridged)。NSURL和CFURLRef就是一對(duì)好基友好例子,在這里其實(shí)CFURLRef和NSURL是可以進(jìn)行替換的。
通常來(lái)說(shuō)為了代碼在底層級(jí)上的正確,在iOS開發(fā)中對(duì)基于C的API的調(diào)用所傳入的參數(shù)一般都是CF對(duì)象,而Objective-C的API調(diào)用都是傳入NSObject對(duì)象。因此在采用自由橋接來(lái)調(diào)用C API的時(shí)候就需要進(jìn)行轉(zhuǎn)換。但是在使用ARC編譯的時(shí)候,因?yàn)閮?nèi)存管理的原因,編譯器需要知道對(duì)這些橋接對(duì)象要實(shí)行什么樣的操作。如果一個(gè)NSURL對(duì)象替代了CFURLRef,那么在作用區(qū)域外,應(yīng)該由誰(shuí)來(lái)決定內(nèi)存釋放和對(duì)象銷毀呢?為了解決這個(gè)問(wèn)題,引入了bridge,bridgetransfer和bridgeretained三個(gè)關(guān)鍵字。關(guān)于選取哪個(gè)關(guān)鍵字做轉(zhuǎn)換,需要由實(shí)際的代碼行為來(lái)決定。如果對(duì)于自由橋接機(jī)制感興趣,大家可以自己找找的相關(guān)內(nèi)容,比如適用類型、內(nèi)部機(jī)制和一個(gè)簡(jiǎn)介~之后我也會(huì)對(duì)這個(gè)問(wèn)題做進(jìn)一步說(shuō)明
回到demo,我們現(xiàn)在在上面的代碼中(CFURLRef)前加上__bridge進(jìn)行轉(zhuǎn)換。然后再運(yùn)行ARC轉(zhuǎn)換工具,這時(shí)候檢查應(yīng)該沒(méi)有其他問(wèn)題了,那么讓我們進(jìn)行轉(zhuǎn)換吧~當(dāng)然在真正轉(zhuǎn)換之前會(huì)有一個(gè)預(yù)覽界面,在這里我們最好檢查一下轉(zhuǎn)換是不是都按照預(yù)想進(jìn)行了..要是出現(xiàn)大面積錯(cuò)誤又沒(méi)有備份或者出現(xiàn)各種意外的話就可以哭了…
前后變化的話比較簡(jiǎn)單,基本就是去掉不需要的代碼和改變property的類型而已,其實(shí)有信心的話不太需要每次都看,但是如果是第一次執(zhí)行ARC轉(zhuǎn)換的操作的話,我還是建議稍微看一下變化,這樣能對(duì)ARC有個(gè)直觀上的了解。檢查一遍,應(yīng)該沒(méi)什么問(wèn)題了..需要注意的是main.m里關(guān)于autoreleasepool的變化以及所有dealloc調(diào)用里的[super dealloc]的刪除,它們同樣是MRC到ARC的主要變化..
好了~轉(zhuǎn)換完成以后我們?cè)賐uild看看..應(yīng)該會(huì)有一些警告。對(duì)于原來(lái)retain的property,比較保險(xiǎn)的做法是轉(zhuǎn)為strong,在LLVM3.0中自動(dòng)轉(zhuǎn)換是這樣做的,但是在3.1中property默認(rèn)并不是strong,這樣在使用property賦值時(shí)存在警告,我們?cè)趐roperty聲明里加上strong就好了~然后就是SVProgressHUD.m里可能存在問(wèn)題,這是由于原作者把release的代碼和其他代碼寫在一行了.導(dǎo)致自動(dòng)轉(zhuǎn)換時(shí)只刪掉了部分,而留下了部分不應(yīng)該存在的代碼,刪掉對(duì)變量的空的調(diào)用就好了..
自動(dòng)轉(zhuǎn)換之后的故事
然后再編譯,沒(méi)有任何錯(cuò)誤和警告了,好棒~等等…我們剛才沒(méi)有對(duì)MainViewController和AFHTTPRequestOperation進(jìn)行處理吧,那么這兩個(gè)文件里應(yīng)該還存在release之類的東西吧..?看一看這兩個(gè)文件,果然有各種release,但是為什么能編譯通過(guò)呢?!明明剛才在自動(dòng)轉(zhuǎn)換前他們還有N多錯(cuò)的嘛…答案很簡(jiǎn)單,在自動(dòng)轉(zhuǎn)換的時(shí)候因?yàn)槲覀儧](méi)有勾選這兩個(gè)文件,因此編譯器在自動(dòng)轉(zhuǎn)換過(guò)后為這兩個(gè)文件標(biāo)記了"不使用ARC編譯"。可以看到在target的Building Phases下,MainViewController.m和AFHTTPRequestOperation.m兩個(gè)文件后面被加上了-fno-objc-arc的編譯標(biāo)記,被加上該標(biāo)記的文件將不使用ARC規(guī)則進(jìn)行編譯。(相對(duì)地,如果你想強(qiáng)制對(duì)某幾個(gè)文件啟用ARC的話,可以為其加上-fobjc-arc標(biāo)記)
提供這樣的編譯標(biāo)記的原因是顯而易見(jiàn)的,因?yàn)榭偸怯幸徊糠值牡谌酱a并沒(méi)有轉(zhuǎn)換為ARC(可能是由于維護(hù)者犯懶或者已經(jīng)終止維護(hù)),所以對(duì)于這部分代碼,為了迅速完成轉(zhuǎn)換,最好是使用-fno-objc-arc標(biāo)記來(lái)禁止在這些源碼上使用ARC。
為了方便查找,再此列出一些在轉(zhuǎn)換時(shí)可能出現(xiàn)的問(wèn)題,當(dāng)然在我們使用ARC時(shí)也需要注意避免代碼中出現(xiàn)這些問(wèn)題:
-
“Cast … requires a bridged cast”
這是我們?cè)赿emo中遇到的問(wèn)題,不再贅述
-
Receiver type ‘X’ for instance message is a forward declaration
這往往是引用的問(wèn)題。ARC要求完整的前向引用,也就是說(shuō)在MRC時(shí)代可能只需要在.h中申明@class就可以,但是在ARC中如果調(diào)用某個(gè)子類中未覆蓋的父類中的方法的話,必須對(duì)父類.h引用,否則無(wú)法編譯。
-
Switch case is in protected scope
現(xiàn)在switch語(yǔ)句必須加上{}了,ARC需要知道局部變量的作用域,加上{}后switch語(yǔ)法更加嚴(yán)格,否則遇到?jīng)]有break的分支的話內(nèi)存管理會(huì)出現(xiàn)問(wèn)題。
-
A name is referenced outside the NSAutoreleasePool scope that it was declared in...
這是由于寫了自己的autoreleasepool,而在轉(zhuǎn)換時(shí)在原來(lái)的pool中申明的變量在新的@autoreleasepool中作用域?qū)⒈痪窒蕖=鉀Q方法是把變量申明拿到pool的申請(qǐng)之前。
-
ARC forbids Objective-C objects in structs or unions
可以說(shuō)ARC所引入的最嚴(yán)格的限制是不能在C結(jié)構(gòu)體中放OC對(duì)象了..因此類似下面這樣的代碼是不可用的
這個(gè)問(wèn)題只有乖乖想辦法了..改變?cè)瓉?lái)的結(jié)構(gòu)什么的..
手動(dòng)轉(zhuǎn)換
剛才做了對(duì)demo的大部分轉(zhuǎn)換,還剩下了MainViewController和AFHTTPRequestOperation是MRC。但是由于使用了-fno-objc-arc,因此現(xiàn)在編譯和運(yùn)行都沒(méi)有問(wèn)題了。下面我們看看如何手動(dòng)把MainViewController轉(zhuǎn)為ARC,這也有助于進(jìn)一步理解ARC的規(guī)則。
首先,我們需要轉(zhuǎn)變一下觀念…對(duì)于MainViewController.h,在.h中申明了兩個(gè)實(shí)例變量:
@interface MainViewController : UIViewController { NSOperationQueue *queue;NSMutableString *currentStringValue; }我們不妨仔細(xì)考慮一下,為什么在interface里出現(xiàn)了實(shí)例變量的申明?通常來(lái)說(shuō),實(shí)例變量只是在類的實(shí)例中被使用,而你所寫的類的使用者并沒(méi)有太多必要了解你的類中有哪些實(shí)例變量。而對(duì)于絕大部分的實(shí)例變量,應(yīng)該都是protected或者private的,對(duì)它們的操作只應(yīng)該用setter和getter,而這正是property所要做的工作。可以說(shuō),將實(shí)例變量寫在頭文件中是一種遺留的陋習(xí)。更好的寫實(shí)例變量名字的地方應(yīng)當(dāng)與類實(shí)現(xiàn)關(guān)系更為密切,為了隱藏細(xì)節(jié),我們應(yīng)該考慮將它們寫在@implementation里。好消息是,在LLVM3.0中,不論是否開啟ARC,編譯器是支持將實(shí)例變量寫到實(shí)現(xiàn)文件中的。甚至如果沒(méi)有特殊需要又用了property,我們都不應(yīng)該寫無(wú)意義的實(shí)例變量申明,因?yàn)樵?#64;synthesize中進(jìn)行綁定時(shí),我們就可以設(shè)置變量名字了,這樣寫的話可以讓代碼更加簡(jiǎn)潔。
在這里我們對(duì)著兩個(gè)實(shí)例變量不需要property(外部成員不應(yīng)當(dāng)能訪問(wèn)到它們),因此我們把申明移到.m里中。修改后的.h是這樣的,十分簡(jiǎn)潔一看就懂~
#import @interface MainViewController : UIViewController @property (nonatomic, retain) IBOutlet UITableView *tableView;?? @property (nonatomic, retain) IBOutlet UISearchBar *searchBar;? @end然后.m的開頭變成這樣:
@implementation MainViewController { NSOperationQueue *queue;? NSMutableString *currentStringValue;? }這樣的寫法讓代碼相當(dāng)靈活,而且不得不承認(rèn).m確實(shí)是這些實(shí)例變量的應(yīng)該在的地方…build一下,沒(méi)問(wèn)題..當(dāng)然對(duì)于SoundEffect類也可以做相似的操作,這會(huì)讓使用你的類的人很開心,因?yàn)?h越簡(jiǎn)單越好..P.S.另外一個(gè)好處可以減少.h里的引用,減少編譯時(shí)間(雖然不明顯=。=)
然后就可以在MainViewController里啟用ARC了,方法很簡(jiǎn)單,刪掉Build Phases里相關(guān)文件的-fno-objc-arc標(biāo)記就可以了~然后..然后當(dāng)然是一大堆錯(cuò)誤啦。我們來(lái)手動(dòng)一個(gè)個(gè)改吧,雖然談不上樂(lè)趣,但是成功以后也會(huì)很有成就~(如果你不幸在啟用ARC后build還是成功了,恭喜你遇到了Xcode的bug,請(qǐng)Cmd+Q然后重新打開Xcode把=_=)
dealloc
紅色最密集的地方是dealloc,因?yàn)槊恳恍卸际莚elease。由于在這里dealloc并沒(méi)有做除了release和super dealloc之外的任何事情,因此簡(jiǎn)單地把整個(gè)方法刪掉就好了。當(dāng)然,在對(duì)象被銷毀時(shí),dealloc還是會(huì)被調(diào)用的,因此我們?cè)谛枰獙?duì)非ARC管理的內(nèi)存進(jìn)行管理和必要的邏輯操作的時(shí)候,還是應(yīng)該保留dealloc的,當(dāng)然這涉及到CF以及以下層的東西:比如對(duì)于retain的CF對(duì)象要CFRelease(),對(duì)于malloc()到堆上的東西要free()掉,對(duì)于添加的observer可以在這里remove,schedule的timer在這里invalidate等等~[super dealloc]這個(gè)消息也不再需要發(fā)了,ARC會(huì)自動(dòng)幫你搞定。
另外,在MRC時(shí)代一個(gè)常做的事情是在dealloc里把指向自己的delegate設(shè)成nil(否則就等著EXCBADACCESS吧 :P),而現(xiàn)在一般delegate都是weak的,因此在self被銷毀后這個(gè)指針自動(dòng)被置成nil了,你不用再為之擔(dān)心,好棒啊..
去掉各種release和autorelease
這個(gè)很直接,沒(méi)有任何問(wèn)題。去掉就行了~不再多說(shuō)
討論一下Property
在MainViewController.m里的類擴(kuò)展中定義了兩個(gè)property:
@interface MainViewController () @property (nonatomic, retain) NSMutableArray *searchResults; @property (nonatomic, retain) SoundEffect *soundEffect;? @end申明的類型是retain,關(guān)于retain,assign和copy的討論已經(jīng)爛大街了,在此不再討論。在MRC的年代使用property可以幫助我們使用dot notation的時(shí)候簡(jiǎn)化對(duì)象的retain和copy,而在ARC時(shí)代,這就顯得比較多余了。在我看來(lái),使用property和點(diǎn)方法來(lái)調(diào)用setter和getter是不必要的。property只在將需要的數(shù)據(jù)在.h中暴露給其他類時(shí)才需要,而在本類中,只需要用實(shí)例變量就可以。(更新,現(xiàn)在筆者在這點(diǎn)上已經(jīng)不糾結(jié)了,隨意就好,自己明白就行。但是也許還是用點(diǎn)方法會(huì)好一些,至少可以分清楚到底是操作了實(shí)例變量還是調(diào)用了setter和getter)。因此我們可以移去searchResults和soundEffect的@property和@synthesize,并將起移到實(shí)例變量申明中:
#import "plementation MainViewController {?NSOperationQueue *queue;?NSMutableString *currentStringValue;NSMutableArray *searchResults;SoundEffect *soundEffect;? }相應(yīng)地,我們需要將對(duì)應(yīng)的self.searchResult和self.soundEffect的self.都去去掉。在這里需要注意的是,雖然我們?nèi)サ袅藄oundEffect的property和synthesize,但是我們依然有一個(gè)lazy loading的方法-(SoundEffect *)soundEffect,神奇之處在于(可能你以前也不知道),點(diǎn)方法并不需要@property關(guān)鍵字的支持,雖然大部分時(shí)間是這么用的..(property只是對(duì)setter或者getter的申明,而點(diǎn)方法是對(duì)其的調(diào)用,在這個(gè)例子的實(shí)現(xiàn)中我們事實(shí)上實(shí)現(xiàn)了-soundEffect這個(gè)getter方法,所以點(diǎn)方法在等號(hào)右邊的getter調(diào)用是沒(méi)有問(wèn)題的)。為了避免誤解,建議把self.soundEffect的getter調(diào)用改寫成[self soundEffect]。
然后我們看看.h里的property~里面有兩個(gè)retain的IBOutlet。retain關(guān)鍵字在ARC中是依舊可用的,它在ARC中所扮演的角色和strong完全一樣。為了避免迷惑,最好在需要的時(shí)候?qū)⑵鋵憺閟trong,那樣更符合ARC的規(guī)則。對(duì)于這兩個(gè)property,我們將其申明為weak(事實(shí)上,如果沒(méi)有特別意外,除了最頂層的IBOutlet意外,自己寫的outlet都應(yīng)該是weak)。通過(guò)加載xib得到的用戶界面,在其從xib文件加載時(shí),就已經(jīng)是view hierarchy的一部分了,而view hierarchy中的指向都是strong的。因此outlet所指向的UI對(duì)象不應(yīng)當(dāng)再被hold一次了。將這些outlet寫為weak的最顯而易見(jiàn)的好處是你就不用再viewDidUnload方法中再將這些outlet設(shè)為nil了(否則就算view被摧毀了,但是由于這些UI對(duì)象還在被outlet指針指向而無(wú)法釋放,代碼簡(jiǎn)潔了很多啊..)。
在我們的demo中將IBOutlet的property改為weak并且刪掉viewDidUnload中關(guān)于這兩個(gè)IBOutlet的內(nèi)容~
總結(jié)一下新加入的property的關(guān)鍵字類型:
- strong 和原來(lái)的retain比較相似,strong的property將對(duì)應(yīng)__strong的指針,它將持有所指向的對(duì)象
- weak 不持有所指向的對(duì)象,而且當(dāng)所指對(duì)象銷毀時(shí)能將自己置為nil,基本所有的outlet都應(yīng)該用weak
- unsafe_unretained 這就是原來(lái)的assign。當(dāng)需要支持iOS4時(shí)需要用到這個(gè)關(guān)鍵字
- copy 和原來(lái)基本一樣..copy一個(gè)對(duì)象并且為其創(chuàng)建一個(gè)strong指針
- assign 對(duì)于對(duì)象來(lái)說(shuō)應(yīng)該永遠(yuǎn)不用assign了,實(shí)在需要的話應(yīng)該用unsafe_unretained代替(基本找不到這種時(shí)候,大部分assign應(yīng)該都被weak替代)。但是對(duì)于基本類型比如int,float,BOOL這樣的東西,還是要用assign。
特別地,對(duì)于NSString對(duì)象,在MRC時(shí)代很多人喜歡用copy,而ARC時(shí)代一般喜歡用strong…(我也不懂為什么..求指教)
自由橋接的細(xì)節(jié)
MainViewController現(xiàn)在剩下的問(wèn)題都是橋接轉(zhuǎn)換問(wèn)題了~有關(guān)橋接的部分有三處:
- (NSString *)CFURLCreateStringByAddingPercentEscapes(…):CFStringRef至NSString *
- (CFStringRef)text:NSString *至CFStringRef
- (CFStringRef)@“!_‘();:@&=+$,/?%#[]":NSString _至CFStringRef
編譯器對(duì)前兩個(gè)進(jìn)行了報(bào)錯(cuò),最后一個(gè)是常量轉(zhuǎn)換不涉及內(nèi)存管理。
關(guān)于toll-free bridged,如果不進(jìn)行細(xì)究,NSString和CFStringRef是一樣的東西,新建一個(gè)CFStringRef可以這么做:
CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!",name];然后,這里alloc了而s1是一個(gè)CF指針,要釋放的話,需要這樣:
CFRelease(s1);相似地可以用CFStringRef來(lái)轉(zhuǎn)成一個(gè)NSString對(duì)象(MRC):
CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault,bytes, kCFStringEncodingMacRoman); NSString *s3 = (NSString *)s2; // release the object when you're done [s3 release];在ARC中,編譯器需要知道這些指針應(yīng)該由誰(shuí)來(lái)負(fù)責(zé)釋放,如果把一個(gè)NSObject看做是CF對(duì)象的話,那么ARC就不再負(fù)責(zé)它的釋放工作(記住ARC是only for NSObject的)。對(duì)于不需要改變持有者的對(duì)象,直接用簡(jiǎn)單的bridge就可以了,比如之前在SoundEffect.m做的轉(zhuǎn)換。在這里對(duì)于(CFStringRef)text這個(gè)轉(zhuǎn)換,ARC已經(jīng)負(fù)責(zé)了text這個(gè)NSObject的內(nèi)存管理,因此這里我們需要一個(gè)簡(jiǎn)單的bridge。而對(duì)于CFURLCreateStringByAddingPercentEscapes方法,方法中的create暗示了這個(gè)方法將形成一個(gè)新的對(duì)象,如果我們不需要NSString轉(zhuǎn)換,那么為了避免內(nèi)存的問(wèn)題,我們需要使用CFRelease來(lái)釋放它。而這里我們需要一個(gè)NSString,因此我們需要告訴編譯器接手它的內(nèi)存管理工作。這里我們使用bridge_transfer關(guān)鍵字,將內(nèi)存管理權(quán)由CF object移交給NSObject(或者說(shuō)ARC)。如果這里我們只用bridge的話,內(nèi)存管理的負(fù)責(zé)人沒(méi)有改變,那么這里就會(huì)出現(xiàn)一個(gè)內(nèi)存泄露。另外有時(shí)候會(huì)看到CFBridgingRelease(),這其實(shí)就是transfer cast的內(nèi)聯(lián)寫法..是一樣的東西。總之,需要記住的原則是,當(dāng)在涉及CF層的東西時(shí),如果函數(shù)名中有含有Create, Copy, 或者Retain之一,就表示返回的對(duì)象的retainCount+1了,對(duì)于這樣的對(duì)象,最安全的做法是將其放在CFBridgingRelease()里,來(lái)平衡retain和release。
還有一種bridge方式,__bridge_retained。顧名思義,這種轉(zhuǎn)換將在轉(zhuǎn)換時(shí)將retainCount加1。和CFBridgingRelease()相似,也有一個(gè)內(nèi)聯(lián)方法CFBridgingRetain()來(lái)負(fù)責(zé)和CFRelease()進(jìn)行平衡。
需要注意的是,并非所有的CF對(duì)象都是自由橋接的,比如Core Graphics中的所有對(duì)象都不是自由橋接的(如CGImage和UIImage,CGColor和UIColor)。另外也不是只有自由橋接對(duì)象才能用bridge來(lái)橋接,一個(gè)很好的特例是void _(指向任意對(duì)象的指針,類似id),對(duì)于void _和任意對(duì)象的轉(zhuǎn)換,一般使用_bridge。(這在將ARC運(yùn)用在Cocos2D中很有用)
終于搞定了
至此整個(gè)工程都ARC了~對(duì)于AFHTTPRequestOperation這樣的不支持ARC的第三方代碼,我們的選擇一般都是就不使用ARC了(或者等開源社區(qū)的大大們更新ARC適配版本)。可以預(yù)見(jiàn),在近期會(huì)有越來(lái)越多的代碼轉(zhuǎn)向ARC,但是也一定會(huì)有大量的代碼暫時(shí)或者永遠(yuǎn)保持MRC等個(gè),所以對(duì)于這些代碼就不用太糾結(jié)了~
寫在最后
寫了那么多,希望你現(xiàn)在能對(duì)ARC有個(gè)比較全面的了解和認(rèn)識(shí)了。ARC肯定是以后的趨勢(shì),也確實(shí)能讓代碼量大大降低,減少了很多無(wú)意義的重復(fù)工作,還提高了app的穩(wěn)定性。但是凡事還是紙上得來(lái)終覺(jué)淺,希望作為開發(fā)者的你,在下一個(gè)工程中去嘗試用用ARC~相信你會(huì)和我一樣,馬上愛(ài)上這種make life easier的方式的~~
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)
總結(jié)
以上是生活随笔為你收集整理的iOS开发ARC入门和使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: aws s3 python_Python
- 下一篇: 流量复制_快速体验之《gor+diffy