ARC自動管理引用計數(shù)

ARC自動管理引用計數(shù)

ARC介紹

ARC其實也是基于引用計數(shù),只是編譯器在編譯時期自動在已有代碼中插入合適的內(nèi)存管理代碼(包括 retain髓窜、release挟憔、copy抠藕、autorelease阴幌、autoreleasepool)以及在 Runtime 做一些優(yōu)化勺阐。

現(xiàn)在的iOS開發(fā)基本都是基于ARC的,所以開發(fā)人員大部分情況都是不需要考慮內(nèi)存管理的矛双,因為編譯器已經(jīng)幫你做了渊抽。為什么說是大部分呢,因為底層的Core Foundation對象由于不在 ARC 的管理下议忽,所以需要自己維護(hù)這些對象的引用計數(shù)腰吟。

還有就算循環(huán)引起情況就算由于互相之間強(qiáng)引用,引用計數(shù)永遠(yuǎn)不會減到0,所以需要自己主動斷開循環(huán)引用毛雇,使引用計數(shù)能夠減少。

所有權(quán)修飾符

Objective-C編程中為了處理對象侦镇,可將變量類型定義為id類型或各種對象類型灵疮。 ARC中id類型和對象類其類型必須附加所有權(quán)修飾符。

其中有以下4種所有權(quán)修飾符:

__strong

__weak

__unsafe_unretaied

__autoreleasing

所有權(quán)修飾符和屬性的修飾符對應(yīng)關(guān)系如下所示:

assign對應(yīng)的所有權(quán)類型是__unsafe_unretained

copy對應(yīng)的所有權(quán)類型是__strong

retain對應(yīng)的所有權(quán)類型是__strong

strong對應(yīng)的所有權(quán)類型是__strong

unsafe_unretained對應(yīng)的所有權(quán)類型是__unsafe_unretained

weak對應(yīng)的所有權(quán)類型是__weak

__strong

__strong表示強(qiáng)引用壳繁,對應(yīng)定義property時用到的strong震捣。當(dāng)對象沒有任何一個強(qiáng)引用指向它時,它才會被釋放闹炉。如果在聲明引用時不加修飾符蒿赢,那么引用將默認(rèn)是強(qiáng)引用。當(dāng)需要釋放強(qiáng)引用指向的對象時渣触,需要保證所有指向?qū)ο髲?qiáng)引用置為 nil羡棵。__strong修飾符是 id 類型和對象類型默認(rèn)的所有權(quán)修飾符。

原理:

{id__strongobj = [[NSObjectalloc] init];}

//編譯器的模擬代碼idobj = objc_msgSend(NSObject,@selector(alloc));objc_msgSend(obj,@selector(init));// 出作用域的時候調(diào)用objc_release(obj);

雖然ARC有效時不能使用release方法嗅钻,但由此可知編譯器自動插入了release皂冰。

對象是通過除alloc、new养篓、copy秃流、multyCopy外方法產(chǎn)生的情況

{id__strongobj = [NSMutableArrayarray];}

結(jié)果與之前稍有不同:

//編譯器的模擬代碼idobj = objc_msgSend(NSMutableArray,@selector(array));objc_retainAutoreleasedReturnValue(obj);objc_release(obj);

objc_retainAutoreleasedReturnValue函數(shù)主要用于優(yōu)化程序的運行。它是用于持有(retain)對象的函數(shù)柳弄,它持有的對象應(yīng)為返回注冊在autoreleasePool中對象的方法舶胀,或是函數(shù)的返回值。像該源碼這樣碧注,在調(diào)用array類方法之后嚣伐,由編譯器插入該函數(shù)。

而這種objc_retainAutoreleasedReturnValue函數(shù)是成對存在的应闯,與之對應(yīng)的函數(shù)是objc_autoreleaseReturnValue纤控。它用于array類方法返回對象的實現(xiàn)上。下面看看NSMutableArray類的array方法通過編譯器進(jìn)行了怎樣的轉(zhuǎn)換:

+ (id)array{return[[NSMutableArrayalloc] init];}

//編譯器模擬代碼+ (id)array{idobj = objc_msgSend(NSMutableArray,@selector(alloc));? ? objc_msgSend(obj,@selector(init));// 代替我們調(diào)用了autorelease方法returnobjc_autoreleaseReturnValue(obj);}

我們可以看見調(diào)用了objc_autoreleaseReturnValue函數(shù)且這個函數(shù)會返回注冊到自動釋放池的對象碉纺,但是船万,這個函數(shù)有個特點,它會查看調(diào)用方的命令執(zhí)行列表骨田,如果發(fā)現(xiàn)接下來會調(diào)用objc_retainAutoreleasedReturnValue則不會將返回的對象注冊到autoreleasePool中而僅僅返回一個對象耿导。達(dá)到了一種最優(yōu)效果。如下圖:

need-to-insert-img

__weak

__weak表示弱引用态贤,對應(yīng)定義property時用到的 weak舱呻。弱引用不會影響對象的釋放,而當(dāng)對象被釋放時,所有指向它的弱引用都會自定被置為 nil箱吕,這樣可以防止野指針芥驳。使用__weak修飾的變量,即是使用注冊到autoreleasePool中的對象茬高。__weak最常見的一個作用就是用來避免循環(huán)循環(huán)兆旬。需要注意的是,__weak修飾符只能用于 iOS5 以上的版本怎栽,在 iOS4 及更低的版本中使用__unsafe_unretained修飾符來代替丽猬。

__weak 的幾個使用場景:

在 Delegate 關(guān)系中防止循環(huán)引用。

在 Block 中防止循環(huán)引用熏瞄。

用來修飾指向由 Interface Builder 創(chuàng)建的控件脚祟。比如:@property (weak, nonatomic) IBOutlet UIButton *testButton;。

原理:

{id__weakobj = [[NSObjectalloc] init];? ? }

編譯器轉(zhuǎn)換后的代碼如下:

idobj;idtmp = objc_msgSend(NSObject,@selector(alloc));? ? objc_msgSend(tmp,@selector(init));? ? objc_initweak(&obj,tmp);? ? objc_release(tmp);? ? objc_destroyWeak(&object);

對于__weak內(nèi)存管理也借助了類似于引用計數(shù)表的散列表强饮,它通過對象的內(nèi)存地址做為key由桌,而對應(yīng)的__weak修飾符變量的地址作為value注冊到weak表中,在上述代碼中objc_initweak就是完成這部分操作胡陪,而objc_destroyWeak

則是銷毀該對象對應(yīng)的value沥寥。當(dāng)指向的對象被銷毀時,會通過其內(nèi)存地址柠座,去weak表中查找對應(yīng)的__weak修飾符變量艾帐,將其從weak表中刪除浩销。所以前硫,weak在修飾只是讓weak表增加了記錄沒有引起引用計數(shù)表的變化宇挫。

對象通過objc_release釋放對象內(nèi)存的動作如下:

objc_release

因為引用計數(shù)為0所以執(zhí)行dealloc

_objc_rootDealloc

objc_dispose

objc_destructInstance

objc_clear_deallocating

而在對象被廢棄時最后調(diào)用了objc_clear_deallocating,該函數(shù)的動作如下:

從weak表中獲取已廢棄對象內(nèi)存地址對應(yīng)的所有記錄

將已廢棄對象內(nèi)存地址對應(yīng)的記錄中所有以weak修飾的變量都置為nil

從weak表刪除已廢棄對象內(nèi)存地址對應(yīng)的記錄

根據(jù)已廢棄對象內(nèi)存地址從引用計數(shù)表中找到對應(yīng)記錄刪除

據(jù)此可以解釋為什么對象被銷毀時對應(yīng)的weak指針變量全部都置為nil吹泡,同時骤星,也看出來銷毀weak步驟較多,如果大量使用weak的話會增加CPU的負(fù)荷爆哑。

還需要確認(rèn)一點是:使用__weak修飾符的變量洞难,即是使用注冊到autoreleasePool中的對象。

{id__weakobj1 = obj;NSLog(@"obj2-%@",obj1);? ? }

編譯器轉(zhuǎn)換上述代碼如下:

idobj1;? ? objc_initweak(&obj1,obj);idtmp = objc_loadWeakRetained(&obj1);? ? objc_autorelease(tmp);NSLog(@"%@",tmp);? ? objc_destroyWeak(&obj1);

objc_loadWeakRetained函數(shù)獲取附有__weak修飾符變量所引用的對象并retain,objc_autorelease函數(shù)將對象放入autoreleasePool中揭朝,據(jù)此當(dāng)我們訪問weak修飾指針指向的對象時队贱,實際上是訪問注冊到自動釋放池的對象。因此潭袱,如果大量使用weak的話柱嫌,在我們?nèi)ピL問weak修飾的對象時,會有大量對象注冊到自動釋放池,這會影響程序的性能屯换。

解決方案:要訪問weak修飾的變量時编丘,先將其賦給一個strong變量,然后進(jìn)行訪問

為什么訪問weak修飾的對象就會訪問注冊到自動釋放池的對象呢?

因為weak不會引起對象的引用計數(shù)器變化,因此嘉抓,該對象在運行過程中很有可能會被釋放索守。所以,需要將對象注冊到自動釋放池中并在autoreleasePool銷毀時釋放對象占用的內(nèi)存掌眠。

__unsafe_unretained

ARC 是在 iOS5 引入的蕾盯,而__unsafe_unretained這個修飾符主要是為了在ARC剛發(fā)布時兼容iOS4以及版本更低的系統(tǒng),因為這些版本沒有弱引用機(jī)制蓝丙。這個修飾符在定義property時對應(yīng)的是unsafe_unretained。__unsafe_unretained修飾的指針純粹只是指向?qū)ο笸希瑳]有任何額外的操作渺尘,不會去持有對象使得對象的 retainCount +1。而在指向的對象被釋放時依然原原本本地指向原來的對象地址说敏,不會被自動置為 nil鸥跟,所以成為了野指針,非常不安全盔沫。

__unsafe_unretained的應(yīng)用場景:

在 ARC 環(huán)境下但是要兼容 iOS4.x 的版本医咨,用__unsafe_unretained替代 __weak 解決強(qiáng)循環(huán)循環(huán)的問題。

__autoreleasing

將對象賦值給附有__autoreleasing修飾符的變量等同于MRC時調(diào)用對象的autorelease方法架诞。

@autoeleasepool {// 如果看了上面__strong的原理拟淮,就知道實際上對象已經(jīng)注冊到自動釋放池里面了 id__autoreleasing obj = [[NSObjectalloc] init];? ? }

編譯器轉(zhuǎn)換上述代碼如下:

idpool = objc_autoreleasePoolPush();idobj = objc_msgSend(NSObject,@selector(alloc));? ? objc_msgSend(obj,@selector(init));? ? objc_autorelease(obj);? ? objc_autoreleasePoolPop(pool);@autoreleasepool{id__autoreleasing obj = [NSMutableArrayarray];? ? }

編譯器轉(zhuǎn)換上述代碼如下:

idpool = objc_autoreleasePoolPush();idobj = objc_msgSend(NSMutableArray,@selector(array));? ? objc_retainAutoreleasedReturnValue(obj);? ? objc_autorelease(obj);? ? objc_autoreleasePoolPop(pool);

上面兩種方式,雖然第二種持有對象的方法從alloc方法變?yōu)榱薿bjc_retainAutoreleasedReturnValue函數(shù)谴忧,都是通過objc_autorelease很泊,注冊到autoreleasePool中。

循環(huán)引用

什么是循環(huán)引用沾谓?循環(huán)引用就是在兩個對象互相之間強(qiáng)引用了委造,引用計數(shù)都加1了,我們前面說過均驶,只有當(dāng)引用計數(shù)減為0時對象才釋放昏兆。但是這兩個的引用計數(shù)都依賴于對方,所以也就導(dǎo)致了永遠(yuǎn)無法釋放妇穴。

最容易產(chǎn)生循環(huán)引用的兩種情況就是Delegate和Block爬虱。所以我們就引入了弱引用這種概念,即弱引用雖然持有對象伟骨,但是并不增加引用計數(shù)饮潦,這樣就避免了循環(huán)引用的產(chǎn)生。也就是我們上面所說的所有權(quán)修飾符__weak的作用携狭。關(guān)于原理在__weak部分也有描述继蜡,簡單的描述就是每一個擁有弱引用的對象都有一張表來保存弱引用的指針地址,但是這個弱引用并不會使對象引用計數(shù)加1,所以當(dāng)這個對象的引用計數(shù)變?yōu)?時稀并,系統(tǒng)就通過這張表仅颇,找到所有的弱引用指針把它們都置成nil。

所以在ARC中做內(nèi)存管理主要就是發(fā)現(xiàn)這些內(nèi)存泄漏碘举,關(guān)于內(nèi)存泄漏Instrument為我們提供了 Allocations/Leaks 這樣的工具用來檢測忘瓦。但是個人覺得還是很麻煩的,大部分時候內(nèi)存泄漏并不會引起應(yīng)用的崩潰或者報錯之類的引颈,所以我們也不會每次主動的去查看當(dāng)前代碼有沒有內(nèi)存泄漏之類的耕皮。

這里有一個微信讀書團(tuán)隊開源的工具MLeaksFinder,它可以在你程序運行期間蝙场,如果有內(nèi)存泄漏就會彈出提示告訴你泄漏的地方凌停。

具體原理如下

我們知道,當(dāng)一個 UIViewController 被 pop 或 dismiss 后售滤,該 UIViewController 包括它的 view罚拟,view 的 subviews 等等將很快被釋放(除非你把它設(shè)計成單例,或者持有它的強(qiáng)引用完箩,但一般很少這樣做)赐俗。于是,我們只需在一個 ViewController 被 pop 或 dismiss 一小段時間后弊知,看看該 UIViewController阻逮,它的 view,view 的 subviews 等等是否還存在吉捶。

具體的方法是夺鲜,為基類 NSObject 添加一個方法 -willDealloc 方法,該方法的作用是呐舔,先用一個弱指針指向 self币励,并在一小段時間(3秒)后,通過這個弱指針調(diào)用 -assertNotDealloc珊拼,而 -assertNotDealloc 主要作用是直接中斷言食呻。

- (BOOL)willDealloc {? ? __weakidweakSelf =self;? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{? ? ? ? [weakSelf assertNotDealloc];? ? });returnYES;}- (void)assertNotDealloc {NSAssert(NO, @“”);}

這樣,當(dāng)我們認(rèn)為某個對象應(yīng)該要被釋放了澎现,在釋放前調(diào)用這個方法仅胞,如果3秒后它被釋放成功,weakSelf 就指向 nil剑辫,不會調(diào)用到 -assertNotDealloc 方法干旧,也就不會中斷言,如果它沒被釋放(泄露了)妹蔽,-assertNotDealloc 就會被調(diào)用中斷言椎眯。這樣挠将,當(dāng)一個 UIViewController 被 pop 或 dismiss 時(我們認(rèn)為它應(yīng)該要被釋放了),我們遍歷該 UIViewController 上的所有 view编整,依次調(diào) -willDealloc舔稀,若3秒后沒被釋放,就會中斷言掌测。

Core Foundation 對象的內(nèi)存管理

底層的 Core Foundation 對象内贮,在創(chuàng)建時大多以 XxxCreateWithXxx 這樣的方式創(chuàng)建,例如:

// 創(chuàng)建一個 CFStringRef 對象CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);

// 創(chuàng)建一個 CTFontRef 對象

CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);

對于這些對象的引用計數(shù)的修改汞斧,要相應(yīng)的使用 CFRetain 和 CFRelease 方法夜郁。如下所示:

// 創(chuàng)建一個 CTFontRef 對象CTFontReffontRef =CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize,NULL);// 引用計數(shù)加 1CFRetain(fontRef);// 引用計數(shù)減 1CFRelease(fontRef);

對于CFRetain和CFRelease兩個方法,讀者可以直觀地認(rèn)為粘勒,這與 Objective-C 對象的retain和release方法等價拂酣。

所以對于底層Core Foundation對象,我們只需要延續(xù)以前手工管理引用計數(shù)的辦法即可仲义。

除此之外,還有另外一個問題需要解決剑勾。在 ARC 下埃撵,我們有時需要將一個Core Foundation對象轉(zhuǎn)換成一個Objective-C對象,這個時候我們需要告訴編譯器虽另,轉(zhuǎn)換過程中的引用計數(shù)需要做如何的調(diào)整暂刘。這就引入了bridge相關(guān)的關(guān)鍵字,以下是這些關(guān)鍵字的說明:

__bridge: 只做類型轉(zhuǎn)換捂刺,不修改相關(guān)對象的引用計數(shù)谣拣,原來的 Core Foundation 對象在不用時,需要調(diào)用 CFRelease 方法族展。

__bridge_retained:類型轉(zhuǎn)換后森缠,將相關(guān)對象的引用計數(shù)加 1,原來的 Core Foundation 對象在不用時仪缸,需要調(diào)用 CFRelease 方法贵涵。

__bridge_transfer:類型轉(zhuǎn)換后,將該對象的引用計數(shù)交給 ARC 管理恰画,Core Foundation 對象在不用時宾茂,不再需要調(diào)用 CFRelease 方法。

參考

iOS中堆和棧的區(qū)別

Objective-C 中的內(nèi)存分配

iOS內(nèi)存管理

理解 iOS 的內(nèi)存管理

鏈接:http://www.reibang.com/p/c3344193ce02

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拴还,一起剝皮案震驚了整個濱河市跨晴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌片林,老刑警劉巖端盆,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怀骤,死亡現(xiàn)場離奇詭異,居然都是意外死亡爱谁,警方通過查閱死者的電腦和手機(jī)晒喷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來访敌,“玉大人凉敲,你說我怎么就攤上這事∷峦” “怎么了爷抓?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長阻塑。 經(jīng)常有香客問我蓝撇,道長,這世上最難降的妖魔是什么陈莽? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任渤昌,我火速辦了婚禮,結(jié)果婚禮上走搁,老公的妹妹穿的比我還像新娘独柑。我一直安慰自己,他們只是感情好私植,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著索绪,像睡著了一般贫悄。 火紅的嫁衣襯著肌膚如雪瑞驱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼拥刻。 笑死吴汪,一個胖子當(dāng)著我的面吹牛漾橙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淘捡,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼膘魄,長吁一口氣:“原來是場噩夢啊……” “哼瓣距!你這毒婦竟也來了呐芥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后诞帐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愕鼓,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡磺送,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年甲捏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡疙赠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出富寿,到底是詐尸還是另有隱情,我是刑警寧澤银萍,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布泞坦,位于F島的核電站贰锁,受9級特大地震影響赃梧,放射性物質(zhì)發(fā)生泄漏锣险。R本人自食惡果不足惜崖咨,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一歌豺、第九天 我趴在偏房一處隱蔽的房頂上張望推穷。 院中可真熱鬧,春花似錦类咧、人聲如沸骗露。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至珊随,卻和暖如春述寡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叶洞。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工鲫凶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衩辟。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓螟炫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艺晴。 傳聞我的和親對象是個殘疾皇子昼钻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容