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 方法。
參考
鏈接:http://www.reibang.com/p/c3344193ce02