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

前言

什么是內(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)形式如下:

image

引用計(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)存上的分配如下圖所示:

2018-08-25 _9_08_52.png

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)作如下:

  1. objc_release
  2. 因?yàn)橐糜?jì)數(shù)為0所以執(zhí)行dealloc
  3. _objc_rootDealloc
  4. objc_dispose
  5. objc_destructInstance
  6. objc_clear_deallocating

而在對(duì)象被廢棄時(shí)最后調(diào)用了objc_clear_deallocating浸船,該函數(shù)的動(dòng)作如下:

  1. 從weak表中獲取已廢棄對(duì)象內(nèi)存地址對(duì)應(yīng)的所有記錄
  2. 將已廢棄對(duì)象內(nèi)存地址對(duì)應(yīng)的記錄中所有以weak修飾的變量都置為nil
  3. 從weak表刪除已廢棄對(duì)象內(nèi)存地址對(duì)應(yīng)的記錄
  4. 根據(jù)已廢棄對(duì)象內(nèi)存地址從引用計(jì)數(shù)表中找到對(duì)應(yīng)記錄刪除
  5. 據(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)引用的兩種情況就是DelegateBlock扁远。所以我們就引入了弱引用這種概念俊鱼,即弱引用雖然持有對(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ì)于 CFRetainCFRelease 兩個(gè)方法,讀者可以直觀地認(rèn)為脓斩,這與 Objective-C 對(duì)象的 retainrelease 方法等價(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 方法占业。

參考

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

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

iOS內(nèi)存管理

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绒怨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谦疾,更是在濱河造成了極大的恐慌,老刑警劉巖犬金,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件念恍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡晚顷,警方通過(guò)查閱死者的電腦和手機(jī)峰伙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)该默,“玉大人瞳氓,你說(shuō)我怎么就攤上這事∷ㄐ洌” “怎么了匣摘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)裹刮。 經(jīng)常有香客問(wèn)我音榜,道長(zhǎng),這世上最難降的妖魔是什么捧弃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任赠叼,我火速辦了婚禮,結(jié)果婚禮上违霞,老公的妹妹穿的比我還像新娘嘴办。我一直安慰自己,他們只是感情好买鸽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布涧郊。 她就那樣靜靜地躺著,像睡著了一般癞谒。 火紅的嫁衣襯著肌膚如雪底燎。 梳的紋絲不亂的頭發(fā)上刃榨,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音双仍,去河邊找鬼枢希。 笑死,一個(gè)胖子當(dāng)著我的面吹牛朱沃,可吹牛的內(nèi)容都是我干的苞轿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逗物,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搬卒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起翎卓,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤契邀,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后失暴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體坯门,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年逗扒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了古戴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矩肩,死狀恐怖现恼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情黍檩,我是刑警寧澤叉袍,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站建炫,受9級(jí)特大地震影響畦韭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肛跌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一艺配、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衍慎,春花似錦转唉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春砖织,著一層夾襖步出監(jiān)牢的瞬間款侵,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工侧纯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留新锈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓眶熬,卻偏偏與公主長(zhǎng)得像妹笆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娜氏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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