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