引用計數(shù):一個簡單而有效的管理對象生命周期的方式,Objective-C和Swift的內(nèi)存管理方式都是基于引用計數(shù)的随珠。
引用計數(shù)的原理:當(dāng)創(chuàng)建一個對象蝶俱,它的引用計數(shù)為1笛匙,當(dāng)有一個新的指針只想這個對象侨把,它的引用計數(shù)+1,當(dāng)某個指針不再指向這個對象時妹孙,我們將其引用計數(shù)-1秋柄,當(dāng)對象的引用計數(shù)為0時,說明這個對象不再被任何指針指向了涕蜂,這時候华匾,就可以將這個對象銷毀,回收內(nèi)存机隙。
NSObject *object = [[NSObject alloc]init]; //創(chuàng)建對象 引用計數(shù) +1
NSLog(@"Reference Count = %u",[object retainCount]); // Reference Count = 1
[object release]; //引用計數(shù) -1 對象會被釋放掉蜘拉。
為什么要使用引用計數(shù)?
- 當(dāng)我們在一個函數(shù)內(nèi)使用一個臨時對象時有鹿,通常不需要修改它的引用計數(shù)旭旭,只需要在函數(shù)返回前將該對象銷毀即可。
- 引用計數(shù)正真排上用場的是在面向?qū)ο蟮某绦虻脑O(shè)計架構(gòu)中葱跋,用于對象之間傳遞和共享數(shù)據(jù)持寄。
舉個例子:
當(dāng)對象A生成了一個對象M源梭,需要調(diào)用對象B的一個方法,將對象M作為參數(shù)傳給對象B稍味。在沒有引用計數(shù)的情況下废麻,一般內(nèi)存管理的原則是“誰申請誰釋放”,那么對象A就需要在對B不需要對象M的時候講M銷毀模庐,但對象B可能只是臨時用一下對象M烛愧,也可能覺得對象M也很重要,講它設(shè)置成自己的一個成員變量掂碱,在這種情況下怜姿,就不好確定什么時候該釋放對象M。
- 方法一:暴力釋放:就是在在A對象調(diào)用完對象B后疼燥,立馬銷毀對象M沧卢,然后B再復(fù)制一份對象M生成M2,然后自己管理M2的生命周期醉者,但是這樣做就會有一個很大的問題但狭,內(nèi)存申請、復(fù)制湃交、釋放的工作會耗費很多的性能熟空。本來一個可以復(fù)用的對象,因為不方便管理其生命周期搞莺,就簡單的把它銷毀,又重新構(gòu)造一份一模一樣的掂咒,實在很影響性能才沧。
- 方法二: 對象A在構(gòu)造玩對象M后,始終不銷毀對象M绍刮,由對象B去完成對象M的銷毀工作温圆,如果對象B需要長時間使用對象M就不銷毀它。如果只是臨時用一下孩革,就可以用完立馬銷毀岁歉。雖然看似很好的解決了對象復(fù)制的問題,但是它強烈的依賴于A和B兩個對象的配合膝蜈,代碼維護者需要明確的記住這種編程約定锅移。而且,由于對象M的申請實在對象A中饱搏,釋放在對象B中非剃,是的它的內(nèi)存管理代碼非常的分散于不同的對象中,管理起來十分的費勁推沸。如果這時候再復(fù)雜一點备绽,對象B需要向C也傳遞對象M券坞,那么這個時候?qū)ο驝中又不能讓對象C管理。所以這種方式帶來的復(fù)雜性更大肺素,更不可取恨锚。
所以引用計數(shù)就很好的解決了這個問題,哪些對象需要長時間的使用這個對象倍靡,就把它的引用計數(shù)+1猴伶,使用完之后-1,。所有的對象都遵循這個規(guī)則的話菌瘫,對象的生命周期就可以完全交給引用計數(shù)了蜗顽。我們也可以方便的享受對象帶來的好處
不要向已經(jīng)釋放的對象發(fā)送消息
一個對象如果已經(jīng)被釋放回收,它所占的內(nèi)容被復(fù)用了雨让,那么就會造成程序異常崩潰雇盖。
NSObject *object = [[NSObject alloc]init]; //創(chuàng)建對象 引用計數(shù) +1
NSLog(@"Reference Count = %u",[object retainCount]); // Reference Count = 1
[object release]; //引用計數(shù) -1 對象會被釋放掉。
NSLog(@"Reference Count = %u",[object retainCount]); // Reference Count = 1
上面這段代碼中兩次打印結(jié)果都是 Reference Count = 1栖忠,因為當(dāng)系統(tǒng)最后一次執(zhí)行release的時候崔挖,系統(tǒng)已經(jīng)知道了你這個對象會被立馬釋放,所以沒有必要再將retainCount 減 1 了庵寞,因為不管減不減 1 都會被釋放回收狸相,所以對應(yīng)該對象的內(nèi)存區(qū)域和retainCount的值也就變的毫無意義。不將這個值從1變成0捐川,反而可以減少一次內(nèi)存操作脓鹃,加速對象的回收。對于內(nèi)存古沥,我們要錙銖必較瘸右。
循環(huán)引用
雖然引用計數(shù)這種管理內(nèi)存的方式很簡單,但是有一個較為大的問題就是岩齿,它不能解決循環(huán)引用太颤。在block的使用中經(jīng)常會遇到循環(huán)引用的問題。
舉個例子:
- A盹沈、B連個對象龄章,相互引用了對方作為自己的成員變量。只有自己銷毀時乞封,才將成員變量的引用計數(shù)減1.因為A的銷毀依賴于B的銷毀做裙,同樣的,B的銷毀也依賴于A的銷毀歌亲。這樣就造成了死循環(huán)一樣菇用,即使外界已經(jīng)沒有人持有它們了,但是它們還是不能夠銷毀釋放陷揪,這就是 循環(huán)引用惋鸥。
- 不止兩個對象會造成循環(huán)引用杂穷,多個對象也會造成循環(huán)引用,形成類似一個閉環(huán)卦绣,多個對象依次持有耐量。在我們的實際開發(fā)中,當(dāng)這個閉環(huán)越大的時候滤港,就越難發(fā)現(xiàn)廊蜒。
如何解決循環(huán)引用呢?
- 明確知道會在這里造成循環(huán)引用溅漾,根據(jù)具體業(yè)務(wù)邏輯在不需要的時候山叮,合理的位置上主動斷開閉環(huán)中的引用,使得對象回收添履。 這種方式需要程序員顯示的手動去釋放對象屁倔,相當(dāng)于回到以前的“誰創(chuàng)建誰釋放”的內(nèi)存管理年代,它就需要程序員自己有能去發(fā)現(xiàn)循環(huán)引用暮胧,并且知道在什么時機斷開循環(huán)引用回收內(nèi)存锐借。比較不常用。
-
弱引用:弱引用往衷,雖然持有對象钞翔,但是不會增加引用計數(shù),這樣就避免了循環(huán)引用的問題席舍。在iOS開發(fā)中布轿,弱引用通常運用在delegate和block中。
在Xocede中自帶有檢測循環(huán)引用的工具来颤。Xcode的工具集Instruments可以很方便的檢測循環(huán)引用問題驮捍。
操作步驟:菜單欄 Product → Profile,程序運行完后選擇 Leaks ,choose選擇脚曾。
ARC
ARC(Automatic Reference Count 簡稱ARC)自動引用計數(shù),用于內(nèi)存管理的技術(shù)启具。ARC是WWDC2011大會提出來的技術(shù)本讥,蘋果將OSX上的垃圾回收機制飛起,采用ARC替代鲁冯,現(xiàn)在的ARC技術(shù)已經(jīng)很成熟了拷沸。筆者使用ARC的這幾年,基本沒有發(fā)生過內(nèi)存泄漏問題薯演。
ARC并不是GC(Garbage Collection 垃圾回收器)撞芍,它只是一種代碼靜態(tài)分析(Static Analyzer)工具,背后的原理是依賴編譯器的靜態(tài)分析能力跨扮,通過在編譯時找出合理的插入引用計數(shù)管理代碼序无,從而提高iOS開發(fā)人員的開發(fā)效率验毡。
Apple的文檔里是這么定義ARC的:“自動引用計數(shù)(ARC)是一個編譯器級的功能,它能簡化Cocoa應(yīng)用中對象生命周期管理(內(nèi)存管理)的流程帝嗡。”
ARC的實現(xiàn)機制是在編譯期完成的晶通。在編譯階段,編譯器將在項目代碼中自動為分配對象插入retain哟玷、release和autorelease狮辽,且插入的代碼不可見。
作用:
-.降低內(nèi)存泄露等風(fēng)險 巢寡;
-.減少代碼工作量喉脖,使開發(fā)者只需專注于業(yè)務(wù)邏輯
特別需要注意的
- ARC確實可以解決90%以上的內(nèi)存管理問題,但是還是有10%需要開發(fā)者自己處理的抑月,尤為要提的就是與底層 Core Foundation 對象交互的那部分树叽,底層的 Core Foundation 對象不在ARC的管理下,所以需要手動處理這些對象的引用計數(shù)爪幻。
- 還有一個就是在過度使用block的時候菱皆,會出現(xiàn)循環(huán)引用的問題,但是又不能解決挨稿。
Core Foundation :
//創(chuàng)建一個CFStringRef對象
CFStringRef str = CFStringCreateWithCString(KCFAllocatorDefault, "Hello world",KCFStringEncodingUTF8);
//創(chuàng)建一個CTFontRef對象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"AriaMT",fontSize,NULL);
//如果要對這些對象的引用計數(shù)做修改就要使用對應(yīng)的 CFRetain 和 CFRelease 方法仇轻。
CFRetain(fonnRef) //引用計數(shù) +1
CFRelease(fontRef) //引用計數(shù) -1
當(dāng)在ARC模式下使用 Core Foundation 時,我們需要將一個CF對象轉(zhuǎn)成OC對象的時候奶甘,需要告訴編譯器篷店,轉(zhuǎn)換過程中的引用計數(shù)如何調(diào)整。在轉(zhuǎn)換時需要引入一些關(guān)鍵字:
-
__bridge
:只做類型轉(zhuǎn)換臭家,不修改相關(guān)的引用計數(shù)疲陕。原理的CF對象不在時,需要調(diào)用CFRelease钉赁。(反之亦然) -
__bridge_retained
:類型轉(zhuǎn)換后蹄殃,將相關(guān)對象引用計數(shù)加1,原來的CF對象不在時你踩,需要調(diào)用CFRelease诅岩。 -
__bridge_transfer
:類型轉(zhuǎn)換后,將該對象的引用計數(shù)交給ARC管理带膜,CF對象不存在時吩谦,不需要調(diào)用CFRelease。
所以膝藕,我們需要根據(jù)具體的業(yè)務(wù)邏輯式廷,合理使用上面這三種轉(zhuǎn)換的關(guān)鍵字,就可以解決Core Foundation 與 Objective-C 對象相互轉(zhuǎn)換的問題了芭挽。