前言
什么是內(nèi)存管理?是指軟件運(yùn)行時(shí)對(duì)計(jì)算機(jī)內(nèi)存資源的分配和使用的技術(shù)。其最主要的目的是如何高效卸例,快速的分配姥闪,并且在適當(dāng)?shù)臅r(shí)候釋放和回收內(nèi)存資源始苇。
我們本篇學(xué)習(xí)的就是iOS開(kāi)發(fā)中是如何對(duì)內(nèi)存進(jìn)行管理的。其中有部分章節(jié)是從前人的文章中搬運(yùn)過(guò)來(lái)整理而成筐喳,這些文章里已經(jīng)對(duì)部分知識(shí)點(diǎn)解釋的很清楚明了了催式,我也沒(méi)有更好的表達(dá)方式,所以站在巨人的肩膀上避归,我只是一個(gè)整理者加了部分自己的理解荣月。
內(nèi)存分配
首先既然我們需要對(duì)內(nèi)存進(jìn)行管理,就需要知道內(nèi)存是怎么分配的梳毙,是分配在哪里的哺窄?
在iOS中數(shù)據(jù)是存在在堆和棧中的,然而我們的內(nèi)存管理管理的是堆上的內(nèi)存账锹,棧上的內(nèi)存并不需要我們管理萌业。
- 非OC對(duì)象(基礎(chǔ)數(shù)據(jù)類(lèi)型)存儲(chǔ)在棧上
- OC對(duì)象存儲(chǔ)在堆上
如下面一段代碼:
int a = 10; //棧
int b = 20; //棧
Car *c = [[Car alloc] init];
在內(nèi)存中的表現(xiàn)形式如下:
引用計(jì)數(shù)
引用計(jì)數(shù)解釋
引用計(jì)數(shù)是計(jì)算機(jī)編程語(yǔ)言中的一種內(nèi)存管理技術(shù),是指將資源(可以是對(duì)象奸柬、內(nèi)存或磁盤(pán)空間等等)的被引用次數(shù)保存起來(lái)生年,當(dāng)被引用次數(shù)變?yōu)榱銜r(shí)就將其釋放的過(guò)程。使用引用計(jì)數(shù)技術(shù)可以實(shí)現(xiàn)自動(dòng)資源管理的目的廓奕。同時(shí)引用計(jì)數(shù)還可以指使用引用計(jì)數(shù)技術(shù)回收未使用資源的垃圾回收算法抱婉。
當(dāng)創(chuàng)建一個(gè)對(duì)象的實(shí)例并在堆上申請(qǐng)內(nèi)存時(shí),對(duì)象的引用計(jì)數(shù)就為1桌粉,在其他對(duì)象中需要持有這個(gè)對(duì)象時(shí)蒸绩,就需要把該對(duì)象的引用計(jì)數(shù)加1,需要釋放一個(gè)對(duì)象時(shí)铃肯,就將該對(duì)象的引用計(jì)數(shù)減1患亿,直至對(duì)象的引用計(jì)數(shù)為0,對(duì)象的內(nèi)存會(huì)被立刻釋放缘薛。
在遙遠(yuǎn)的以前窍育,iOS開(kāi)發(fā)的內(nèi)存管理是手動(dòng)處理引用計(jì)數(shù)卡睦,在合適的地方使引用計(jì)數(shù)-1,直到減為0漱抓,內(nèi)存釋放”矶停現(xiàn)在的iOS開(kāi)發(fā)內(nèi)存管理使用的是ARC,自動(dòng)管理引用計(jì)數(shù)乞娄,會(huì)根據(jù)引用計(jì)數(shù)自動(dòng)監(jiān)視對(duì)象的生存周期瞬逊,實(shí)現(xiàn)方式是在編譯時(shí)期自動(dòng)在已有代碼中插入合適的內(nèi)存管理代碼以及在 Runtime 做一些優(yōu)化。
文藝的解釋
記得在《尋夢(mèng)環(huán)游記》里對(duì)于一個(gè)人的死亡是這樣定義的:當(dāng)這個(gè)這個(gè)世界上最后一個(gè)人都忘記你時(shí)仪或,就迎來(lái)了終極死亡确镊。類(lèi)比于引用計(jì)數(shù),就是每有一個(gè)人記得你時(shí)你的引用計(jì)數(shù)加1范删,每有一個(gè)人忘記你時(shí)蕾域,你的引用計(jì)數(shù)減1,當(dāng)所有人都忘記你時(shí)到旦,你就消失了旨巷,也就是從內(nèi)存中釋放了。
如果再深一層添忘,包含我們后面要介紹的ARC中的強(qiáng)引用和弱引用的話采呐,那這個(gè)記住的含義就不一樣了。強(qiáng)引用就是你摯愛(ài)的親人搁骑,朋友等對(duì)你比較重要的人記得你斧吐,你的引用計(jì)數(shù)才加1。
而弱引用就是那種路人仲器,一面之緣的人煤率,他們只是對(duì)你有一個(gè)印象,他們記得你是沒(méi)有用的娄周,你的引用計(jì)數(shù)不會(huì)加1涕侈。當(dāng)你摯愛(ài)的人都忘記你時(shí),你的引用計(jì)數(shù)歸零煤辨,你就從這個(gè)世界上消失了,而這些路人只是感覺(jué)到自己記憶中忽然少了些什么而已木张。
代碼測(cè)試
我們創(chuàng)建一個(gè)工程众辨,在Build Phases
里設(shè)置AppDelegate的Compiler Flags
為-fno-objc-arc
來(lái)開(kāi)啟手動(dòng)管理引用計(jì)數(shù)的模式。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSObject *object = [[NSObject alloc] init];
NSLog(@"\n 引用計(jì)數(shù) = %lu \n 對(duì)象內(nèi)存 = %p \n object指針內(nèi)存地址 = %x", (unsigned long)[object retainCount], object, &object);
self.property = object;
NSLog(@"\n 引用計(jì)數(shù) = %lu \n 對(duì)象內(nèi)存 = %p \n object指針內(nèi)存地址 = %x \n property指針內(nèi)存地址 = %x", (unsigned long)[object retainCount], object, &object, &_property);
[object release];
NSLog(@"\n 引用計(jì)數(shù) = %lu \n 對(duì)象內(nèi)存 = %p \n object指針內(nèi)存地址 = %x \n property指針內(nèi)存地址 = %x", (unsigned long)[object retainCount], object, &object, &_property);
return YES;
}
輸出:
2018-08-25 21:01:01.323677+0800 test[26304:9610044]
引用計(jì)數(shù) = 1
對(duì)象內(nèi)存 = 0x60000000e290
object指針內(nèi)存地址 = ee0fee28
2018-08-25 21:01:01.323880+0800 test[26304:9610044]
引用計(jì)數(shù) = 2
對(duì)象內(nèi)存 = 0x60000000e290
object指針內(nèi)存地址 = ee0fee28
property指針內(nèi)存地址 = 301b8
2018-08-25 21:01:01.324088+0800 test[26304:9610044]
引用計(jì)數(shù) = 1
對(duì)象內(nèi)存 = 0x60000000e290
object指針內(nèi)存地址 = ee0fee28
property指針內(nèi)存地址 = 301b8
我們看到object
持有對(duì)象引用計(jì)數(shù)+1為1舷礼,然后self.property
又持有了對(duì)象鹃彻,引用計(jì)數(shù)再+1為2,然后我們主動(dòng)釋放object
妻献,引用計(jì)數(shù)-1變?yōu)?蛛株。我們能看到[object release]
釋放后指向?qū)ο蟮闹羔樔跃捅槐A粼趏bject這個(gè)變量中团赁,只是對(duì)象的引用計(jì)數(shù)-1了而已。
對(duì)應(yīng)的內(nèi)存上的分配如下圖所示:
MRC手動(dòng)管理引用計(jì)數(shù)
在MRC中增加的引用計(jì)數(shù)都是需要自己手動(dòng)釋放的谨履,所以我們需要知道哪些方式會(huì)引起引用計(jì)數(shù)+1欢摄;
對(duì)象操作 | OC中對(duì)應(yīng)的方法 | 引用計(jì)數(shù)的變化 |
---|---|---|
生成并持有對(duì)象 | alloc/new/copy/mutableCopy等 | +1 |
持有對(duì)象 | retain | +1 |
釋放對(duì)象 | release | -1 |
廢棄對(duì)象 | dealloc | - |
四個(gè)法則
- 自己生成的對(duì)象,自己持有笋粟。
- 非自己生成的對(duì)象怀挠,自己也能持有。
- 不在需要自己持有對(duì)象的時(shí)候害捕,釋放绿淋。
- 非自己持有的對(duì)象無(wú)需釋放。
/*
* 自己生成并持有該對(duì)象
*/
id obj0 = [[NSObeject alloc] init];
id obj1 = [NSObeject new];
/*
* 持有非自己生成的對(duì)象
*/
id obj = [NSArray array]; // 非自己生成的對(duì)象尝盼,且該對(duì)象存在吞滞,但自己不持有
[obj retain]; // 自己持有對(duì)象
/*
* 不在需要自己持有的對(duì)象的時(shí)候,釋放
*/
id obj = [[NSObeject alloc] init]; // 此時(shí)持有對(duì)象
[obj release]; // 釋放對(duì)象
/*
* 指向?qū)ο蟮闹羔樔跃捅槐A粼趏bj這個(gè)變量中
* 但對(duì)象已經(jīng)釋放盾沫,不可訪問(wèn)
*/
/*
* 非自己持有的對(duì)象無(wú)法釋放
*/
id obj = [NSArray array]; // 非自己生成的對(duì)象裁赠,且該對(duì)象存在,但自己不持有
[obj release]; // ~~~此時(shí)將運(yùn)行時(shí)crash 或編譯器報(bào)error~~~ 非 ARC 下疮跑,調(diào)用該方法會(huì)導(dǎo)致編譯器報(bào) issues组贺。此操作的行為是未定義的,可能會(huì)導(dǎo)致運(yùn)行時(shí) crash 或者其它未知行為
非自己生成的對(duì)象祖娘,且該對(duì)象存在失尖,但自己不持有
其中關(guān)于非自己生成的對(duì)象,且該對(duì)象存在渐苏,但自己不持有
是如何實(shí)現(xiàn)的呢掀潮?這個(gè)特性是使用autorelease來(lái)實(shí)現(xiàn)的,示例代碼如下:
- (id) getAObjNotRetain {
id obj = [[NSObject alloc] init]; // 自己持有對(duì)象
[obj autorelease]; // 取得的對(duì)象存在琼富,但自己不持有該對(duì)象
return obj;
}
使用autorelease
方法可以使取得的對(duì)象存在仪吧,但自己不持有對(duì)象。autorelease 使得對(duì)象在超出生命周期后能正確的被釋放(通過(guò)調(diào)用release方法)鞠眉。在調(diào)用 release 后薯鼠,對(duì)象會(huì)被立即釋放,而調(diào)用 autorelease
后械蹋,對(duì)象不會(huì)被立即釋放出皇,而是注冊(cè)到 autoreleasepool
中,經(jīng)過(guò)一段時(shí)間后 pool結(jié)束哗戈,此時(shí)調(diào)用release方法郊艘,對(duì)象被釋放。
像[NSMutableArray array]
[NSArray array]
都可以取得誰(shuí)都不持有的對(duì)象,這些方法都是通過(guò)autorelease
實(shí)現(xiàn)的纱注。
ARC自動(dòng)管理引用計(jì)數(shù)
ARC介紹
ARC其實(shí)也是基于引用計(jì)數(shù)畏浆,只是編譯器在編譯時(shí)期自動(dòng)在已有代碼中插入合適的內(nèi)存管理代碼(包括 retain、release狞贱、copy刻获、autorelease、autoreleasepool)以及在 Runtime 做一些優(yōu)化斥滤。
現(xiàn)在的iOS開(kāi)發(fā)基本都是基于ARC的将鸵,所以開(kāi)發(fā)人員大部分情況都是不需要考慮內(nèi)存管理的,因?yàn)榫幾g器已經(jīng)幫你做了佑颇。為什么說(shuō)是大部分呢顶掉,因?yàn)榈讓拥?Core Foundation
對(duì)象由于不在 ARC 的管理下,所以需要自己維護(hù)這些對(duì)象的引用計(jì)數(shù)挑胸。
還有就算循環(huán)引起情況就算由于互相之間強(qiáng)引用痒筒,引用計(jì)數(shù)永遠(yuǎn)不會(huì)減到0,所以需要自己主動(dòng)斷開(kāi)循環(huán)引用茬贵,使引用計(jì)數(shù)能夠減少簿透。
所有權(quán)修飾符
Objective-C編程中為了處理對(duì)象,可將變量類(lèi)型定義為id類(lèi)型或各種對(duì)象類(lèi)型解藻。 ARC中id類(lèi)型和對(duì)象類(lèi)其類(lèi)型必須附加所有權(quán)修飾符老充。
其中有以下4種所有權(quán)修飾符:
- __strong
- __weak
- __unsafe_unretaied
- __autoreleasing
所有權(quán)修飾符和屬性的修飾符對(duì)應(yīng)關(guān)系如下所示:
-
assign
對(duì)應(yīng)的所有權(quán)類(lèi)型是__unsafe_unretained
-
copy
對(duì)應(yīng)的所有權(quán)類(lèi)型是__strong
-
retain
對(duì)應(yīng)的所有權(quán)類(lèi)型是__strong
-
strong
對(duì)應(yīng)的所有權(quán)類(lèi)型是__strong
-
unsafe_unretained
對(duì)應(yīng)的所有權(quán)類(lèi)型是__unsafe_unretained
-
weak
對(duì)應(yīng)的所有權(quán)類(lèi)型是__weak
__strong
__strong
表示強(qiáng)引用,對(duì)應(yīng)定義 property
時(shí)用到的 strong
螟左。當(dāng)對(duì)象沒(méi)有任何一個(gè)強(qiáng)引用指向它時(shí)啡浊,它才會(huì)被釋放。如果在聲明引用時(shí)不加修飾符胶背,那么引用將默認(rèn)是強(qiáng)引用巷嚣。當(dāng)需要釋放強(qiáng)引用指向的對(duì)象時(shí),需要保證所有指向?qū)ο髲?qiáng)引用置為 nil钳吟。__strong
修飾符是 id 類(lèi)型和對(duì)象類(lèi)型默認(rèn)的所有權(quán)修飾符廷粒。
原理:
{
id __strong obj = [[NSObject alloc] init];
}
//編譯器的模擬代碼
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
// 出作用域的時(shí)候調(diào)用
objc_release(obj);
雖然ARC有效時(shí)不能使用release方法,但由此可知編譯器自動(dòng)插入了release红且。
對(duì)象是通過(guò)除alloc坝茎、new、copy暇番、multyCopy外方法產(chǎn)生的情況
{
id __strong obj = [NSMutableArray array];
}
結(jié)果與之前稍有不同:
//編譯器的模擬代碼
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue
函數(shù)主要用于優(yōu)化程序的運(yùn)行景东。它是用于持有(retain)對(duì)象的函數(shù),它持有的對(duì)象應(yīng)為返回注冊(cè)在autoreleasePool
中對(duì)象的方法奔誓,或是函數(shù)的返回值。像該源碼這樣,在調(diào)用array類(lèi)方法之后厨喂,由編譯器插入該函數(shù)和措。
而這種objc_retainAutoreleasedReturnValue
函數(shù)是成對(duì)存在的,與之對(duì)應(yīng)的函數(shù)是objc_autoreleaseReturnValue
蜕煌。它用于array類(lèi)方法返回對(duì)象的實(shí)現(xiàn)上派阱。下面看看NSMutableArray
類(lèi)的array
方法通過(guò)編譯器進(jìn)行了怎樣的轉(zhuǎn)換:
+ (id)array
{
return [[NSMutableArray alloc] init];
}
//編譯器模擬代碼
+ (id)array
{
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_msgSend(obj,@selector(init));
// 代替我們調(diào)用了autorelease方法
return objc_autoreleaseReturnValue(obj);
}
我們可以看見(jiàn)調(diào)用了objc_autoreleaseReturnValue
函數(shù)且這個(gè)函數(shù)會(huì)返回注冊(cè)到自動(dòng)釋放池的對(duì)象,但是斜纪,這個(gè)函數(shù)有個(gè)特點(diǎn)贫母,它會(huì)查看調(diào)用方的命令執(zhí)行列表,如果發(fā)現(xiàn)接下來(lái)會(huì)調(diào)用objc_retainAutoreleasedReturnValue
則不會(huì)將返回的對(duì)象注冊(cè)到autoreleasePool
中而僅僅返回一個(gè)對(duì)象盒刚。達(dá)到了一種最優(yōu)效果腺劣。如下圖:
__weak
__weak
表示弱引用,對(duì)應(yīng)定義 property
時(shí)用到的 weak因块。弱引用不會(huì)影響對(duì)象的釋放橘原,而當(dāng)對(duì)象被釋放時(shí),所有指向它的弱引用都會(huì)自定被置為 nil涡上,這樣可以防止野指針趾断。使用__weak修飾的變量,即是使用注冊(cè)到autoreleasePool
中的對(duì)象吩愧。__weak
最常見(jiàn)的一個(gè)作用就是用來(lái)避免循環(huán)循環(huán)芋酌。需要注意的是,__weak
修飾符只能用于 iOS5 以上的版本雁佳,在 iOS4 及更低的版本中使用 __unsafe_unretained
修飾符來(lái)代替脐帝。
__weak 的幾個(gè)使用場(chǎng)景:
- 在 Delegate 關(guān)系中防止循環(huán)引用。
- 在 Block 中防止循環(huán)引用甘穿。
- 用來(lái)修飾指向由 Interface Builder 創(chuàng)建的控件腮恩。比如:@property (weak, nonatomic) IBOutlet UIButton *testButton;。
原理:
{
id __weak obj = [[NSObject alloc] init];
}
編譯器轉(zhuǎn)換后的代碼如下:
id obj;
id tmp = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_initweak(&obj,tmp);
objc_release(tmp);
objc_destroyWeak(&object);
對(duì)于__weak
內(nèi)存管理也借助了類(lèi)似于引用計(jì)數(shù)表的散列表温兼,它通過(guò)對(duì)象的內(nèi)存地址做為key秸滴,而對(duì)應(yīng)的__weak
修飾符變量的地址作為value注冊(cè)到weak表中,在上述代碼中objc_initweak
就是完成這部分操作募判,而objc_destroyWeak
則是銷(xiāo)毀該對(duì)象對(duì)應(yīng)的value荡含。當(dāng)指向的對(duì)象被銷(xiāo)毀時(shí),會(huì)通過(guò)其內(nèi)存地址届垫,去weak表中查找對(duì)應(yīng)的__weak
修飾符變量释液,將其從weak
表中刪除。所以装处,weak
在修飾只是讓weak
表增加了記錄沒(méi)有引起引用計(jì)數(shù)表的變化误债。
對(duì)象通過(guò)objc_release
釋放對(duì)象內(nèi)存的動(dòng)作如下:
- objc_release
- 因?yàn)橐糜?jì)數(shù)為0所以執(zhí)行dealloc
- _objc_rootDealloc
- objc_dispose
- objc_destructInstance
- objc_clear_deallocating
而在對(duì)象被廢棄時(shí)最后調(diào)用了objc_clear_deallocating
浸船,該函數(shù)的動(dòng)作如下:
- 從weak表中獲取已廢棄對(duì)象內(nèi)存地址對(duì)應(yīng)的所有記錄
- 將已廢棄對(duì)象內(nèi)存地址對(duì)應(yīng)的記錄中所有以weak修飾的變量都置為nil
- 從weak表刪除已廢棄對(duì)象內(nèi)存地址對(duì)應(yīng)的記錄
- 根據(jù)已廢棄對(duì)象內(nèi)存地址從引用計(jì)數(shù)表中找到對(duì)應(yīng)記錄刪除
- 據(jù)此可以解釋為什么對(duì)象被銷(xiāo)毀時(shí)對(duì)應(yīng)的weak指針變量全部都置為nil,同時(shí)寝蹈,也看出來(lái)銷(xiāo)毀weak步驟較多李命,如果大量使用weak的話會(huì)增加CPU的負(fù)荷。
還需要確認(rèn)一點(diǎn)是:使用__weak修飾符的變量箫老,即是使用注冊(cè)到autoreleasePool
中的對(duì)象封字。
{
id __weak obj1 = obj;
NSLog(@"obj2-%@",obj1);
}
編譯器轉(zhuǎn)換上述代碼如下:
id obj1;
objc_initweak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&obj1);
objc_loadWeakRetained
函數(shù)獲取附有__weak修飾符變量所引用的對(duì)象并retain
, objc_autorelease
函數(shù)將對(duì)象放入autoreleasePool中,據(jù)此當(dāng)我們?cè)L問(wèn)weak修飾指針指向的對(duì)象時(shí)耍鬓,實(shí)際上是訪問(wèn)注冊(cè)到自動(dòng)釋放池的對(duì)象阔籽。因此,如果大量使用weak的話牲蜀,在我們?nèi)ピL問(wèn)weak修飾的對(duì)象時(shí)笆制,會(huì)有大量對(duì)象注冊(cè)到自動(dòng)釋放池,這會(huì)影響程序的性能。
解決方案:要訪問(wèn)weak修飾的變量時(shí)各薇,先將其賦給一個(gè)strong變量项贺,然后進(jìn)行訪問(wèn)
為什么訪問(wèn)weak修飾的對(duì)象就會(huì)訪問(wèn)注冊(cè)到自動(dòng)釋放池的對(duì)象呢?
因?yàn)閣eak不會(huì)引起對(duì)象的引用計(jì)數(shù)器變化,因此峭判,該對(duì)象在運(yùn)行過(guò)程中很有可能會(huì)被釋放开缎。所以,需要將對(duì)象注冊(cè)到自動(dòng)釋放池中并在autoreleasePool銷(xiāo)毀時(shí)釋放對(duì)象占用的內(nèi)存林螃。
__unsafe_unretained
ARC 是在 iOS5 引入的奕删,而 __unsafe_unretained
這個(gè)修飾符主要是為了在ARC剛發(fā)布時(shí)兼容iOS4以及版本更低的系統(tǒng),因?yàn)檫@些版本沒(méi)有弱引用機(jī)制疗认。這個(gè)修飾符在定義property時(shí)對(duì)應(yīng)的是unsafe_unretained
完残。__unsafe_unretained
修飾的指針純粹只是指向?qū)ο螅瑳](méi)有任何額外的操作横漏,不會(huì)去持有對(duì)象使得對(duì)象的 retainCount +1谨设。而在指向的對(duì)象被釋放時(shí)依然原原本本地指向原來(lái)的對(duì)象地址,不會(huì)被自動(dòng)置為 nil缎浇,所以成為了野指針扎拣,非常不安全。
__unsafe_unretained
的應(yīng)用場(chǎng)景:
在 ARC 環(huán)境下但是要兼容 iOS4.x 的版本素跺,用__unsafe_unretained
替代 __weak 解決強(qiáng)循環(huán)循環(huán)的問(wèn)題二蓝。
__autoreleasing
將對(duì)象賦值給附有__autoreleasing
修飾符的變量等同于MRC時(shí)調(diào)用對(duì)象的autorelease方法。
@autoeleasepool {
// 如果看了上面__strong的原理指厌,就知道實(shí)際上對(duì)象已經(jīng)注冊(cè)到自動(dòng)釋放池里面了
id __autoreleasing obj = [[NSObject alloc] init];
}
編譯器轉(zhuǎn)換上述代碼如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
編譯器轉(zhuǎn)換上述代碼如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
上面兩種方式刊愚,雖然第二種持有對(duì)象的方法從alloc方法變?yōu)榱?code>objc_retainAutoreleasedReturnValue函數(shù),都是通過(guò)objc_autorelease
踩验,注冊(cè)到autoreleasePool
中鸥诽。
循環(huán)引用
什么是循環(huán)引用商玫?循環(huán)引用就是在兩個(gè)對(duì)象互相之間強(qiáng)引用了,引用計(jì)數(shù)都加1了衙传,我們前面說(shuō)過(guò)决帖,只有當(dāng)引用計(jì)數(shù)減為0時(shí)對(duì)象才釋放。但是這兩個(gè)的引用計(jì)數(shù)都依賴(lài)于對(duì)方蓖捶,所以也就導(dǎo)致了永遠(yuǎn)無(wú)法釋放。
最容易產(chǎn)生循環(huán)引用的兩種情況就是Delegate
和Block
扁远。所以我們就引入了弱引用這種概念俊鱼,即弱引用雖然持有對(duì)象,但是并不增加引用計(jì)數(shù)畅买,這樣就避免了循環(huán)引用的產(chǎn)生并闲。也就是我們上面所說(shuō)的所有權(quán)修飾符__weak
的作用。關(guān)于原理在__weak
部分也有描述谷羞,簡(jiǎn)單的描述就是每一個(gè)擁有弱引用的對(duì)象都有一張表來(lái)保存弱引用的指針地址帝火,但是這個(gè)弱引用并不會(huì)使對(duì)象引用計(jì)數(shù)加1,所以當(dāng)這個(gè)對(duì)象的引用計(jì)數(shù)變?yōu)?時(shí)湃缎,系統(tǒng)就通過(guò)這張表犀填,找到所有的弱引用指針把它們都置成nil。
所以在ARC中做內(nèi)存管理主要就是發(fā)現(xiàn)這些內(nèi)存泄漏嗓违,關(guān)于內(nèi)存泄漏Instrument為我們提供了 Allocations/Leaks 這樣的工具用來(lái)檢測(cè)九巡。但是個(gè)人覺(jué)得還是很麻煩的,大部分時(shí)候內(nèi)存泄漏并不會(huì)引起應(yīng)用的崩潰或者報(bào)錯(cuò)之類(lèi)的蹂季,所以我們也不會(huì)每次主動(dòng)的去查看當(dāng)前代碼有沒(méi)有內(nèi)存泄漏之類(lèi)的冕广。
這里有一個(gè)微信讀書(shū)團(tuán)隊(duì)開(kāi)源的工具MLeaksFinder,它可以在你程序運(yùn)行期間偿洁,如果有內(nèi)存泄漏就會(huì)彈出提示告訴你泄漏的地方撒汉。
具體原理如下:
我們知道,當(dāng)一個(gè) UIViewController 被 pop 或 dismiss 后涕滋,該 UIViewController 包括它的 view睬辐,view 的 subviews 等等將很快被釋放(除非你把它設(shè)計(jì)成單例,或者持有它的強(qiáng)引用何吝,但一般很少這樣做)溉委。于是,我們只需在一個(gè) ViewController 被 pop 或 dismiss 一小段時(shí)間后爱榕,看看該 UIViewController瓣喊,它的 view,view 的 subviews 等等是否還存在黔酥。
具體的方法是藻三,為基類(lèi) NSObject 添加一個(gè)方法 -willDealloc 方法洪橘,該方法的作用是,先用一個(gè)弱指針指向 self棵帽,并在一小段時(shí)間(3秒)后熄求,通過(guò)這個(gè)弱指針調(diào)用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中斷言逗概。
- (BOOL)willDealloc {
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf assertNotDealloc];
});
return YES;
}
- (void)assertNotDealloc {
NSAssert(NO, @“”);
}
這樣弟晚,當(dāng)我們認(rèn)為某個(gè)對(duì)象應(yīng)該要被釋放了,在釋放前調(diào)用這個(gè)方法逾苫,如果3秒后它被釋放成功卿城,weakSelf 就指向 nil,不會(huì)調(diào)用到 -assertNotDealloc 方法铅搓,也就不會(huì)中斷言瑟押,如果它沒(méi)被釋放(泄露了),-assertNotDealloc 就會(huì)被調(diào)用中斷言星掰。這樣多望,當(dāng)一個(gè) UIViewController 被 pop 或 dismiss 時(shí)(我們認(rèn)為它應(yīng)該要被釋放了),我們遍歷該 UIViewController 上的所有 view氢烘,依次調(diào) -willDealloc怀偷,若3秒后沒(méi)被釋放,就會(huì)中斷言威始。
Core Foundation 對(duì)象的內(nèi)存管理
底層的 Core Foundation 對(duì)象枢纠,在創(chuàng)建時(shí)大多以 XxxCreateWithXxx 這樣的方式創(chuàng)建,例如:
// 創(chuàng)建一個(gè) CFStringRef 對(duì)象
CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);
// 創(chuàng)建一個(gè) CTFontRef 對(duì)象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
對(duì)于這些對(duì)象的引用計(jì)數(shù)的修改黎棠,要相應(yīng)的使用 CFRetain 和 CFRelease 方法晋渺。如下所示:
// 創(chuàng)建一個(gè) CTFontRef 對(duì)象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
// 引用計(jì)數(shù)加 1
CFRetain(fontRef);
// 引用計(jì)數(shù)減 1
CFRelease(fontRef);
對(duì)于 CFRetain
和 CFRelease
兩個(gè)方法,讀者可以直觀地認(rèn)為脓斩,這與 Objective-C 對(duì)象的 retain
和 release
方法等價(jià)木西。
所以對(duì)于底層 Core Foundation
對(duì)象,我們只需要延續(xù)以前手工管理引用計(jì)數(shù)的辦法即可随静。
除此之外八千,還有另外一個(gè)問(wèn)題需要解決。在 ARC 下燎猛,我們有時(shí)需要將一個(gè) Core Foundation
對(duì)象轉(zhuǎn)換成一個(gè) Objective-C
對(duì)象恋捆,這個(gè)時(shí)候我們需要告訴編譯器,轉(zhuǎn)換過(guò)程中的引用計(jì)數(shù)需要做如何的調(diào)整重绷。這就引入了bridge
相關(guān)的關(guān)鍵字沸停,以下是這些關(guān)鍵字的說(shuō)明:
__bridge: 只做類(lèi)型轉(zhuǎn)換,不修改相關(guān)對(duì)象的引用計(jì)數(shù)昭卓,原來(lái)的 Core Foundation 對(duì)象在不用時(shí)愤钾,需要調(diào)用 CFRelease 方法瘟滨。
__bridge_retained:類(lèi)型轉(zhuǎn)換后,將相關(guān)對(duì)象的引用計(jì)數(shù)加 1能颁,原來(lái)的 Core Foundation 對(duì)象在不用時(shí)杂瘸,需要調(diào)用 CFRelease 方法。
__bridge_transfer:類(lèi)型轉(zhuǎn)換后伙菊,將該對(duì)象的引用計(jì)數(shù)交給 ARC 管理败玉,Core Foundation 對(duì)象在不用時(shí),不再需要調(diào)用 CFRelease 方法占业。