iOS內(nèi)存管理

MRC

對象持有討論

先看一個例子:

// 這個方法生成對象并返回
- (id)object
{
    // 通過 alloc/new/copy/mutableCopy 方法生成對象并自己持有
    id obj = [[NSObject alloc] init];
    
    /**
     autorelease使得自己不去持有這個對象
     自己持有對象的話,可以通過[obj release] 去釋放對象,但當(dāng)對象自身autorelease時,釋放是由自身自動釋放的,就不能通過release了(個人理解),這個時候去調(diào)用release會造成崩潰.
     */
    
    [obj autorelease];
    
    return obj;
}

// 方法調(diào)用:

/** 
 這里通過方法 object獲取到對象,這個對象是存在的,但是自己并不持有這個對象,對自己不持有的對象是不能去釋放他的.
 這個對象為什么存在,上面不是自己加了autorelease嗎?
 其實這就是autorelease和release的區(qū)別:調(diào)用release會立即釋放,但autorelease不會,調(diào)用autorelease會將對象注冊到autoreleasepool中,當(dāng)pool結(jié)束時會自動調(diào)用release.這里其實存在著一個哨兵指針. autoreleasepool 的釋放是 NSRunloop 決定的.具體可以參考楊瀟玉的博客.
 */
id obj1 = [obj0 object];

// 如果讓obj1區(qū)持有這個對象呢? 可以調(diào)用retain,他可以將調(diào)用autorelease方法取得的對象變?yōu)樽约撼钟?[obj1 retain];

總結(jié):釋放非自己持有的對象會造成程序崩潰,因此絕不能釋放非自己持有的對象

關(guān)于內(nèi)存釋放的研究

NSAutoreleasePool什么時候釋放?NSAutoreleasePool的生命周期是怎樣的?是在方法體調(diào)用結(jié)束就釋放?其實不是.

在大量產(chǎn)生autorelease對象時,只要不廢棄NSAutoreleasePool對象,那么生成的對象就不能被釋放,因此有時會產(chǎn)生內(nèi)存不足的現(xiàn)象.典型例子是讀入大量圖像時,圖像文件讀入到NSData對象,從中生成UIImage對象.這種清空下會產(chǎn)生大量autorelease對象.這種情況就需要再適當(dāng)?shù)牡胤缴?持有,廢棄NSAutoreleasePool對象.

比如在MRC時可以這樣寫:

for(int i = 0; i<100; i++) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // 讀入圖像,大量產(chǎn)生autorelease對象
    //  ........
    [pool drain];   // 這句代碼可以讓autorelease對象被一起release
}

Zone

我們經(jīng)??吹讲簧俜椒ɡ镉羞@個單詞,比如NSZoneMalloc,NSDefaultMallocZone等等.這個單詞是干什么的呢?

NSZone其實是為了防止內(nèi)存碎片化而引入的結(jié)構(gòu).它ui內(nèi)存分配的區(qū)域本身進(jìn)行多重化管理,根據(jù)對象目的,大小分配內(nèi)存,提高內(nèi)存管理效率.

但是根據(jù)蘋果官方所說,現(xiàn)在的運行時系統(tǒng),內(nèi)存管理的效率本身已經(jīng)很高,使用區(qū)域來管理內(nèi)存反而會引起內(nèi)存使用效率低下及源代碼復(fù)雜化.

ARC

首先看看ARC中的4個修飾符:

  • __strong (默認(rèn)修飾符)
  • __weak
  • __unsafe_unretained
  • __autoreleasing

在MRC中有個問題,通過alloc的方式得到的對象會被自己所持有,而通過比如NSArrayarray方法得到的對象不會被持有:

**  MRC  **
// obj持有對象
id obj = [[NSObject alloc] init];

// array不持有對象
NSArray *arr = [NSMuableArray array];

那在ARC中是怎么樣的呢?

**  ARC  **
// obj持有對象
id obj = [[NSObject alloc] init];
// 相當(dāng)于:
id __strong obj = [[NSObject alloc] init]; // 由于obj為強(qiáng)引用,所以自己持有對象

/**
 這里的array也是持有對象的.
 array類方法讓arr取得 非自己生成并持有  的對象
 但因arr為強(qiáng)引用,所以持有對象
 在超出作用域(方法體)后,強(qiáng)引用失效,會自動釋放自己所持有的對象
*/
NSArray *arr = [NSMuableArray array];
// 相當(dāng)于
NSArray *__strong arr = [NSarray array];

賦值方法對對象的引用

__strong
id __strong objA = [[NSObject alloc] init];  // objA持有對象A的強(qiáng)引用
id __strong objB = [[NSObject alloc] init];  // objB持有對象B的強(qiáng)引用
id __strong objC = nil;  // objC不持有任何對象

/**
 objA持有由objB賦值對象B的強(qiáng)引用
 因為objA被賦值,所以原先持有的對象A的強(qiáng)引用失效
 對象A的所有者不存在,因此廢棄對象A
 
 此時,持有對象B的強(qiáng)引用的變量為
 objA 和 objB
*/
objC = objA;

/**
 objC 持有由objA賦值的對象B的強(qiáng)引用
 此時,持有對象B的強(qiáng)引用的變量為
 objA 和 objB 和 objC
*/
objB = objC;

/**
 nil被賦予了objB,所以objB對對象B的強(qiáng)引用失效
 
 此時,持有對象B的強(qiáng)引用的變量為
 objA 和 objC
 
 
 這里做一個理解,為什么objB = nil, objA還是和objC還是指向?qū)ο驜呢? objA和objC 會變成nil嗎?
 不會.objB = nil;這句話的意思是objB拋棄了原先的對象,重新指向了一個對象(nil),原先的對象B會通過retainCount - 1 的方式回應(yīng)這次'拋棄'. 但如果B不是通過從新指向另一塊內(nèi)存的方式,而是改變自身,比如 [objB addObject],那么他是對自身對象B進(jìn)行的操作,這個時候objA和objC也會相應(yīng)被改變.
*/
objB = nil;

總結(jié):

  • 自己生成的對象,自己所持有
  • 非自己生成的對象,自己也可以持有
  • 不再需要自己持有的對象時釋放
  • 非自己持有的對象無法釋放
__weak

__strong修飾符基本可以完美的進(jìn)行內(nèi)存管理了,但是遇到循環(huán)引用就需要__weak了.

比如兩個Person實例 personApersonB,都有一個id類型的obj屬性, obj屬性被分別賦值 personBpersonA,即:

Person personA = [[Person alloc] init]; // A 對象
Person personB = [[Person alloc] init]; // B 對象

personA.obj = personB;
personB.obj = personA;

這個時候A對象的持有者是兩個 personApersonBobj 屬性;
B對象的持有者也是兩個 personBpersonAobj 屬性;

當(dāng)personA 變量超出其作用域,強(qiáng)引用失效,自動釋放對象A, 當(dāng)personB 變量超出其作用域,強(qiáng)引用失效,自動釋放對象B. 此時,持有 A 對象的強(qiáng)引用變量為對象Bobj,持有B對象的強(qiáng)引用為對象A的obj. 發(fā)生內(nèi)存泄漏.

所謂內(nèi)存泄漏,就是應(yīng)當(dāng)廢棄的對象在超出其生命周期后繼續(xù)存在.

上面的例子,如果A 對象持有自身,即personA.obj = personA,這樣也會造成循環(huán)引用.

還有一個例子,這里做一下分析:

id __weak objA = nil;
{
    id __strong objB = [[NSObject alloc] init];
    objA = objB;
    
    NSLog(@"A = %@",objA);
}

NSLog(@"A = %@",objA);

輸出結(jié)果為:

A = <NSObject: 0x45f140e>
A = (null)

對于id __weak objA = nil;這句代碼,我之前也一直不理解,先不論objA是否=nil, __weak修飾的對象如果沒有強(qiáng)持有者不會立即釋放掉嗎,為什么objA 還存在?

其實,objA是指針,是在棧里的,objA指向的對象(比如對象A)是在堆內(nèi)存里的,確實,對象A釋放后objA也會隨之銷毀,但是由于objA是在棧里的,并且objA其實是autorelease的,該指針只會被標(biāo)記為要釋放,等待autorelease要釋放(drain)時候,objA才會通過哨兵對象確定要被釋放,從而發(fā)送release消息將它釋放掉,所以在這個方法體里,objA還是存在的.所以,在使用附有__weak修飾符的變量時就必定要使用注冊到autorelepool中的對象.其實,__weak申明的變量會自動將對象注冊到autoreleasepool中.

所以,第一次打印,是打印的objA通過弱引用持有的對象,這個對象在方法體結(jié)束后被釋放了,所以第二次打印為null.

<font size='3' color='0000ff'>weak自動置為nil的實現(xiàn)</font>

id __weak obj1 = obj;

這句話做了什么?

  1. 首先,obj1通過obj進(jìn)行初始化,調(diào)用函數(shù)
objc_initWeak(&obj1, obj)

當(dāng)釋放的時候,第二個參數(shù)傳遞0,即

objc_destroyWeak(&obj1, 0)
  1. objc_storeWeak函數(shù)把obj1的地址和被賦值對象通過鍵值對的方式注冊到weak表里.當(dāng)對象釋放的時候,通過廢棄對象的地址作為鍵值進(jìn)行檢索,就能獲取到對應(yīng)__weak變量的地址,然后將所有附有__weak修飾的變量地址全部賦值nil,之后再把她從weak表中移除掉就好了.
  2. 大量使用__weak會消耗相應(yīng)的CPU資源(要注冊weak表,并且一個鍵值,可以注冊多個變量的地址),所以一般只在需要避免循環(huán)引用的時候使用__weak.
__unsafe_unretained

這個修飾符是不安全的.ARC的內(nèi)存管理是編譯器的工作,但附有__unsafe_unretained修飾符的變量不屬于編譯器內(nèi)存管理的對象.

同樣用上面的例子做說明,第一行換成id __unsafe_unretained objA = nil;

打印結(jié)果為:

A = <NSObject: 0x45f140e>
A = <NSObject: 0x45f140e>

奇怪,第二次打印結(jié)果正常,這是怎么回事?

當(dāng)方法體出來后,objB強(qiáng)引用失效,自動釋放自己持有的對象,這個時候沒有強(qiáng)引用去持有這個對象,對象釋放.所以objA變量表示的對象已經(jīng)被廢棄,變成懸垂指針,這是一個錯誤訪問.所以這一次的打印只是碰巧正常運行而已.雖然訪問了已經(jīng)被廢棄的對象,但是應(yīng)用程序在個別運行狀況下才會崩潰.

__autoreleasing

ARC下,autorelease是不能用的(這個修飾符是可以使用的),包括NSAutoreleasePool類也不能用,MRC下是這樣使用的:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; 

drain方法廢棄正在使用的NSautoreleasePool對象的過程為:

- (void)drain
{
    [self dealloc];
}

- (void)dealloc
{
    [self emptyPool];
    [array release];
}

- (void)emptyPool
{
    for (id obj in array){
        [obj release];
    }
}

ARC下,該源代碼寫成如下這樣:

@autoreleasepool {
    
    id __autoreleasing obj = [[NSObject alloc] init]; // ARC無效時會調(diào)用autorelease方法.另外,__autoreleasing修飾符一般是可以非顯示地使用的
    
}

在使用alloc/new/copy/mutableCopy以外的方法來取得對象的時候,該對象會被注冊到autorelpool.編譯器會檢查方法名是否以這幾個單詞開始,如果不是的則自動將返回的對象注冊到autoreleasepoll.

另外,init方法返回值的對象不會注冊到autoreleasepool.

不管是否使用了ARC,調(diào)試用的非公開函數(shù)_objc_autoreleasePoolPrint()都是可以使用的.不過有可能報錯Implicit declaration of function - C99,這種情況可以通過Build Setting -> C Language Dialect修改為GNU99GNU89解決.

使用ARC的時候,對象型變量補(bǔ)鞥呢作為C語言結(jié)構(gòu)體(struct/union)的成員.因為ARC把內(nèi)存管理的工作交給編譯器,那么編譯器必須知道并管理對象的生存周期.要把對象型變量加入到結(jié)構(gòu)體成員中時,可以強(qiáng)制轉(zhuǎn)換為void *或者附加__unsafe_unretained修飾符(因為__unsafe_unretained修飾符修飾的變量不屬于編譯器的內(nèi)存對象).

**  MRC  **
id obj = [[NSObject alloc] init];
void *p = obj;

// 也可以反過來賦值
id obj2 = p;
[obj2 release];
**  ARC  **
// 要使用__bridge橋接轉(zhuǎn)換
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id obj2 = (__bridge id)p;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一.內(nèi)存管理 /引用計數(shù) Objective-C 中的內(nèi)存管理,也就是引用計數(shù) 1.1內(nèi)存管理的思考方式 自己生成...
    sellse閱讀 353評論 0 0
  • 終于明白那些年知其然而不知其所以然的iOS內(nèi)存管理方式 前言 從我開始學(xué)習(xí)iOS的時候,身邊的朋友、網(wǎng)上的博客都告...
    楓宇翔閱讀 7,412評論 8 49
  • 貌似每個iOS開發(fā)者都有一篇屬于自己的內(nèi)存管理,記錄了自己對內(nèi)存管理理解的深度以及廣度,所以我也來記錄一下我的理解...
    Bugfix閱讀 2,387評論 0 3
  • # 前言 反復(fù)地復(fù)習(xí)iOS基礎(chǔ)知識和原理,打磨知識體系是非常重要的,本篇就是重新溫習(xí)iOS的內(nèi)存管理。 內(nèi)存管理是...
    Vein_閱讀 883評論 0 2
  • 你有沒有覺得,當(dāng)你問為什么的時候, 你的思維是停留在過去的。 你有沒有想過,當(dāng)你質(zhì)問別人的時候, 你仍然是糾結(jié)在過...
    人間清醒叔閱讀 664評論 0 2

友情鏈接更多精彩內(nèi)容