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ì)被自己所持有,而通過比如NSArray
的array
方法得到的對(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í)例 personA
和personB
,都有一個(gè)id
類型的obj
屬性, obj
屬性被分別賦值 personB
和personA
,即:
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è) personA
和 personB
的obj
屬性;
B
對(duì)象的持有者也是兩個(gè) personB
和 personA
的obj
屬性;
當(dāng)personA
變量超出其作用域,強(qiáng)引用失效,自動(dòng)釋放對(duì)象A
, 當(dāng)personB
變量超出其作用域,強(qiáng)引用失效,自動(dòng)釋放對(duì)象B
. 此時(shí),持有 A
對(duì)象的強(qiáng)引用變量為對(duì)象B
的obj
,持有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;
這句話做了什么?
- 首先,obj1通過obj進(jìn)行初始化,調(diào)用函數(shù)
objc_initWeak(&obj1, obj)
當(dāng)釋放的時(shí)候,第二個(gè)參數(shù)傳遞0,即
objc_destroyWeak(&obj1, 0)
-
objc_storeWeak
函數(shù)把obj1
的地址和被賦值對(duì)象通過鍵值對(duì)的方式注冊(cè)到weak表里.當(dāng)對(duì)象釋放的時(shí)候,通過廢棄對(duì)象的地址作為鍵值進(jìn)行檢索,就能獲取到對(duì)應(yīng)__weak
變量的地址,然后將所有附有__weak
修飾的變量地址全部賦值nil
,之后再把她從weak
表中移除掉就好了. - 大量使用
__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
修改為GNU99
或GNU89
解決.
使用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;