iOS內(nèi)存管理

MRC

對(duì)象持有討論

先看一個(gè)例子:

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

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

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

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

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

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

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

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

比如在MRC時(shí)可以這樣寫:

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

Zone

我們經(jīng)撤昊剑看到不少方法里有這個(gè)單詞,比如NSZoneMalloc,NSDefaultMallocZone等等.這個(gè)單詞是干什么的呢?

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

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

ARC

首先看看ARC中的4個(gè)修飾符:

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

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

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

// array不持有對(duì)象
NSArray *arr = [NSMuableArray array];

那在ARC中是怎么樣的呢?

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

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

賦值方法對(duì)對(duì)象的引用

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

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

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

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

總結(jié):

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

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

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

Person personA = [[Person alloc] init]; // A 對(duì)象
Person personB = [[Person alloc] init]; // B 對(duì)象

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

這個(gè)時(shí)候A對(duì)象的持有者是兩個(gè) personApersonBobj 屬性;
B對(duì)象的持有者也是兩個(gè) personBpersonAobj 屬性;

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

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

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

還有一個(gè)例子,這里做一下分析:

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

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

輸出結(jié)果為:

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

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

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

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

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

id __weak obj1 = obj;

這句話做了什么?

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

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

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

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

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

打印結(jié)果為:

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

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

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

__autoreleasing

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

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

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

- (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無效時(shí)會(huì)調(diào)用autorelease方法.另外,__autoreleasing修飾符一般是可以非顯示地使用的
    
}

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

另外,init方法返回值的對(duì)象不會(huì)注冊(cè)到autoreleasepool.

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

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

**  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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笔咽,一起剝皮案震驚了整個(gè)濱河市雳锋,隨后出現(xiàn)的幾起案子零如,更是在濱河造成了極大的恐慌焰轻,老刑警劉巖乳讥,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異知残,居然都是意外死亡靠瞎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門求妹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乏盐,“玉大人,你說我怎么就攤上這事制恍「改埽” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵净神,是天一觀的道長(zhǎng)何吝。 經(jīng)常有香客問我,道長(zhǎng)鹃唯,這世上最難降的妖魔是什么爱榕? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮坡慌,結(jié)果婚禮上黔酥,老公的妹妹穿的比我還像新娘。我一直安慰自己洪橘,他們只是感情好絮爷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梨树,像睡著了一般坑夯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抡四,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天柜蜈,我揣著相機(jī)與錄音,去河邊找鬼指巡。 笑死淑履,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的藻雪。 我是一名探鬼主播秘噪,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼勉耀!你這毒婦竟也來了指煎?” 一聲冷哼從身側(cè)響起蹋偏,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎至壤,沒想到半個(gè)月后威始,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡像街,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年黎棠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镰绎。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脓斩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畴栖,到底是詐尸還是另有隱情俭厚,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布驶臊,位于F島的核電站挪挤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏关翎。R本人自食惡果不足惜扛门,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纵寝。 院中可真熱鬧论寨,春花似錦、人聲如沸爽茴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)室奏。三九已至火焰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胧沫,已是汗流浹背昌简。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绒怨,地道東北人纯赎。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像南蹂,于是被迫代替她去往敵國(guó)和親犬金。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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

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