本系列博文總結(jié)自《Pro Multithreading and Memory Management for iOS and OS X with ARC》
在上一篇文章中,我們講了很多關(guān)于 block 和基礎(chǔ)變量的內(nèi)存管理,接著我們聊聊 block 和對(duì)象的內(nèi)存管理,如 block 經(jīng)常會(huì)碰到的循環(huán)引用問(wèn)題等等。
獲取對(duì)象
照例先來(lái)段代碼輕松下,瞧瞧 block 是怎么獲取外部對(duì)象的
/********************** capturing objects **********************/
typedefvoid(^blk_t)(id obj);
blk_tblk;
-(void)viewDidLoad
{
[selfcaptureObject];
blk([[NSObjectalloc]init]);
blk([[NSObjectalloc]init]);
blk([[NSObjectalloc]init]);
}
-(void)captureObject
{
id array=[[NSMutableArrayalloc]init];
blk=[^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld",[array count]);
}copy];
}
翻譯后的關(guān)鍵代碼摘錄如下
/* a struct for the Block and some functions */
struct__main_block_impl_0
{
struct__block_impl impl;
struct__main_block_desc_0*Desc;
id __strong array;
__main_block_impl_0(void*fp,struct__main_block_desc_0*desc,id __strong _array,intflags=0):array(_array)
{
impl.isa=&_NSConcreteStackBlock;
impl.Flags=flags;
impl.FuncPtr=fp;
Desc=desc;
}
};
staticvoid__main_block_func_0(struct__main_block_impl_0*__cself,id obj)
{
id __strong array=__cself->array;
[array addObject:obj];
NSLog(@"array count = %ld",[array count]);
}
staticvoid__main_block_copy_0(struct__main_block_impl_0*dst,__main_block_impl_0*src)
{
_Block_object_assign(&dst->array,src->array,BLOCK_FIELD_IS_OBJECT);
}
staticvoid__main_block_dispose_0(struct__main_block_impl_0*src)
{
_Block_object_dispose(src->array,BLOCK_FIELD_IS_OBJECT);
}
structstaticstruct__main_block_desc_0
{
unsignedlongreserved;
unsignedlongBlock_size;
void(*copy)(struct__main_block_impl_0*,struct__main_block_impl_0*);
void(*dispose)(struct__main_block_impl_0*);
}__main_block_desc_0_DATA={0,
sizeof(struct__main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
/* Block literal and executing the Block */
blk_tblk;
{
id __strong array=[[NSMutableArrayalloc]init];
blk=&__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
array,
0x22000000);
blk=[blk copy];
}
(*blk->impl.FuncPtr)(blk,[[NSObjectalloc]init]);
(*blk->impl.FuncPtr)(blk,[[NSObjectalloc]init]);
(*blk->impl.FuncPtr)(blk,[[NSObjectalloc]init]);
在本例中,當(dāng)變量變量作用域結(jié)束時(shí),array被廢棄,強(qiáng)引用失效,NSMutableArray類的實(shí)例對(duì)象會(huì)被釋放并廢棄。在這危難關(guān)頭,block 及時(shí)調(diào)用了copy方法,在_Block_object_assign中,將array賦值給 block 成員變量并持有。所以上面代碼可以正常運(yùn)行,打印出來(lái)的array count依次遞增。
總結(jié)代碼可正常運(yùn)行的原因關(guān)鍵就在于 block 通過(guò)調(diào)用copy方法,持有了 __strong 修飾的外部變量,使得外部對(duì)象在超出其作用域后得以繼續(xù)存活,代碼正常執(zhí)行。
在以下情形中, block 會(huì)從??截惖蕉眩?/p>
當(dāng) block 調(diào)用copy方法時(shí),如果 block 在棧上,會(huì)被拷貝到堆上;
當(dāng) block 作為函數(shù)返回值返回時(shí),編譯器自動(dòng)將 block 作為_(kāi)Block_copy函數(shù),效果等同于 block 直接調(diào)用copy方法;
當(dāng) block 被賦值給 __strong id 類型的對(duì)象或 block 的成員變量時(shí),編譯器自動(dòng)將 block 作為_(kāi)Block_copy函數(shù),效果等同于 block 直接調(diào)用copy方法;
當(dāng) block 作為參數(shù)被傳入方法名帶有usingBlock的 Cocoa Framework 方法或 GCD 的 API 時(shí)。這些方法會(huì)在內(nèi)部對(duì)傳遞進(jìn)來(lái)的 block 調(diào)用copy或_Block_copy進(jìn)行拷貝;
其實(shí)后三種情況在上篇文章block的自動(dòng)拷貝已經(jīng)做過(guò)說(shuō)明
除此之外,都需要手動(dòng)調(diào)用。
延伸閱讀:Objective-C 結(jié)構(gòu)體中的 __strong 成員變量
注意到__main_block_impl_0結(jié)構(gòu)體有什么異常沒(méi)?在 C 結(jié)構(gòu)體中出現(xiàn)了__strong關(guān)鍵字修飾的變量。
通常情況下, Objective-C 的編譯器因?yàn)闊o(wú)法檢測(cè) C 結(jié)構(gòu)體初始化和釋放的時(shí)間,不能進(jìn)行有效的內(nèi)存管理,所以 Objective-C 的 C 結(jié)構(gòu)體成員是不能用__strong、__weak等等這類關(guān)鍵字修飾。然而 runtime 庫(kù)是可以在運(yùn)行時(shí)檢測(cè)到 block 的內(nèi)存變化,如 block 何時(shí)從棧拷貝到堆,何時(shí)從堆上釋放等等,所以就會(huì)出現(xiàn)上述結(jié)構(gòu)體成員變量用__strong修飾的情況。
__block 變量和對(duì)象
__block 說(shuō)明符可以修飾任何類型的自動(dòng)變量。下面讓我們?cè)倏磦€(gè)小例子,啊,愉快的代碼時(shí)間又到啦。
/******* block 修飾對(duì)象 *******/
__block id obj=[[NSObjectalloc]init];
ARC 下,對(duì)象所有權(quán)修飾符默認(rèn)為_(kāi)_strong,即
__block id __strong obj=[[NSObjectalloc]init];
/******* block 修飾對(duì)象轉(zhuǎn)換后的代碼 *******/
/* struct for __block variable */
struct__Block_byref_obj_0
{
void*__isa;
__Block_byref_obj_0*__forwarding;
int__flags;
int__size;
void(*__Block_byref_id_object_copy)(void*,void*);
void(*__Block_byref_id_object_dispose)(void*);
__strong id obj;
};
staticvoid__Block_byref_id_object_copy_131(void*dst,void*src)
{
_Block_object_assign((char*)dst+40,*(void**)((char*)src+40),131);
}
staticvoid__Block_byref_id_object_dispose_131(void*src)
{
_Block_object_dispose(*(void**)((char*)src+40),131);
}
/* __block variable declaration */
__Block_byref_obj_0 obj={0,
&obj,
0x2000000,
sizeof(__Block_byref_obj_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
[[NSObjectalloc]init]
};
__block id __strong obj的作用和id __strong obj的作用十分類似。當(dāng)__block id __strong obj從棧上拷貝到堆上時(shí),_Block_object_assign被調(diào)用,block 持有obj;當(dāng)__block id __strong obj從堆上被廢棄時(shí),_Block_object_dispose被調(diào)用用以釋放此對(duì)象,block 引用消失。
所以,只要是堆上的__strong修飾符修飾的__block對(duì)象類型的變量,和 block 內(nèi)獲取到的__strong修飾符修飾的對(duì)象類型的變量,編譯器都能對(duì)它們的內(nèi)存進(jìn)行適當(dāng)?shù)墓芾怼?/p>
如果上面的__strong換成__weak,結(jié)果會(huì)怎樣呢?
/********************** capturing __weak objects **********************/
typedefvoid(^blk_t)(id obj);
blk_tblk;
-(void)viewDidLoad
{
[selfcaptureObject];
blk([[NSObjectalloc]init]);
blk([[NSObjectalloc]init]);
blk([[NSObjectalloc]init]);
}
-(void)captureObject
{
id array=[[NSMutableArrayalloc]init];
id __weak array2=array;
blk=[^(id obj){
[array2 addObject:obj];
NSLog(@"array2 count = %ld",[array2 count]);
}copy];
}
結(jié)果是:
array2 count=0
array2 count=0
array2 count=0
原因很簡(jiǎn)單,array2是弱引用,當(dāng)變量作用域結(jié)束,array所指向的對(duì)象內(nèi)存被釋放,array2指向 nil,向 nil 對(duì)象發(fā)送count消息就返回結(jié)果 0 了。
如果__weak再改成__unsafe_unretained呢?__unsafe_unretained修飾的對(duì)象變量指針就相當(dāng)于一個(gè)普通指針。使用這個(gè)修飾符有點(diǎn)需要注意的地方是,當(dāng)指針?biāo)赶虻膶?duì)象內(nèi)存被釋放時(shí),指針變量不會(huì)被置為 nil。所以當(dāng)使用這個(gè)修飾符時(shí),一定要注意不要通過(guò)懸掛指針(指向被廢棄內(nèi)存的指針)來(lái)訪問(wèn)已經(jīng)被廢棄的對(duì)象內(nèi)存,否則程序就會(huì)崩潰。
如果__unsafe_unretained再改成__autoreleasing會(huì)怎樣呢?會(huì)報(bào)錯(cuò),編譯器并不允許你這么干!如果你這么寫(xiě)
__block id __autoreleasing obj=[[NSObjectalloc]init];
編譯器就會(huì)報(bào)下面的錯(cuò)誤,意思就是__block和__autoreleasing不能同時(shí)使用。
error: __block variables cannot have __autoreleasing ownership __block id __autoreleasing obj = [[NSObject alloc] init];
循環(huán)引用
千辛萬(wàn)苦,重頭戲終于來(lái)了。block 如果使用不小心,就容易出現(xiàn)循環(huán)引用,導(dǎo)致內(nèi)存泄露。到底哪里泄露了呢?通過(guò)前面的學(xué)習(xí),各位童鞋應(yīng)該有個(gè)底了,下面就讓我們一起進(jìn)入這泄露地區(qū)瞧瞧,哪兒出了問(wèn)題!
愉快的代碼時(shí)間到
// ARC enabled
/************** MyObject Class **************/
typedefvoid(^blk_t)(void);
@interfaceMyObject:NSObject
{
blk_tblk_;
}
@end
@implementationMyObject
-(id)init
{
self=[superinit];
blk_=^{NSLog(@"self = %@",self);};
returnself;
}
-(void)dealloc
{
NSLog(@"dealloc");
}
@end
/************** main function **************/
intmain()
{
id myObject=[[MyObjectalloc]init];
NSLog(@"%@",myObject);
return0;
}
由于self是__strong修飾,在 ARC 下,當(dāng)編譯器自動(dòng)將代碼中的 block 從??截惖蕉褧r(shí),block 會(huì)強(qiáng)引用和持有self,而self恰好也強(qiáng)引用和持有了 block,就造成了傳說(shuō)中的循環(huán)引用。

由于循環(huán)引用的存在,造成在main()函數(shù)結(jié)束時(shí),內(nèi)存仍然無(wú)法釋放,即內(nèi)存泄露。編譯器也會(huì)給出警告信息
warning: capturing 'self' strongly in this block is likely to lead to a retain cycle [-Warc-retain-cycles]
blk_ = ^{NSLog(@"self = %@", self);};
note: Block will be retained by an object strongly retained by the captured object
blk_ = ^{NSLog(@"self = %@", self);};
為了避免這種情況發(fā)生,可以在變量聲明時(shí)用__weak修飾符修飾變量self,讓 block 不強(qiáng)引用self,從而破除循環(huán)。iOS4 和 Snow Leopard 由于對(duì) weak 的支持不夠完全,可以用__unsafe_unretained代替。
-(id)init
{
self=[superinit];
id __weak tmp=self;
blk_=^{NSLog(@"self = %@",tmp);};
returnself;
}

再看一個(gè)例子
@interfaceMyObject:NSObject
{
blk_tblk_;
id obj_;
}
@end
@implementationMyObject
-(id)init
{
self=[superinit];
blk_=^{NSLog(@"obj_ = %@",obj_);};
returnself;
}
...
...
@end
上面的例子中,雖然沒(méi)有直接使用 self,卻也存在循環(huán)引用的問(wèn)題。因?yàn)閷?duì)于編譯器來(lái)說(shuō),obj_就相當(dāng)于self->obj_,所以上面的代碼就會(huì)變成
blk_=^{NSLog(@"obj_ = %@",self->obj_);};
所以這個(gè)例子只要用__weak,在init方法里面加一行即可
id __weak obj=obj_;
破解循環(huán)引用還有一招,使用 __block 修飾對(duì)象,在 block 內(nèi)將對(duì)象置為 nil 即可,如下
typedefvoid(^blk_t)(void);
@interfaceMyObject:NSObject
{
blk_tblk_;
}
@end
@implementationMyObject
-(id)init
{
self=[superinit];
__block id tmp=self;
blk_=^{
NSLog(@"self = %@",tmp);
tmp=nil;
};
returnself;
}
-(void)execBlock
{
blk_();
}
-(void)dealloc
{
NSLog(@"dealloc");
}
@end
intmain()
{
idobject=[[MyObjectalloc]init];
[objectexecBlock];
return0;
}
這個(gè)例子挺有意思的,如果執(zhí)行execBlock方法,就沒(méi)有循環(huán)引用,如果不執(zhí)行就有循環(huán)引用,挺值得玩味的。一方面,使用 __block 挺危險(xiǎn)的,萬(wàn)一代碼中不執(zhí)行 block ,就造成了循環(huán)引用,而且編譯器還沒(méi)法檢查出來(lái);另一方面,使用 __block 可以讓我們通過(guò) __block 變量去控制對(duì)象的生命周期,而且有可能在一些非常老舊的 MRC 代碼中,由于不支持 __weak,我們可以使用此方法來(lái)代替 __unsafe_unretained,從而避免懸掛指針的問(wèn)題。
還有個(gè)值得一提的時(shí),在 MRC 下,使用 __block 說(shuō)明符也可以避免循環(huán)引用。因?yàn)楫?dāng) block 從棧拷貝到堆時(shí),__block 對(duì)象類型的變量不會(huì)被 retain,沒(méi)有 __block 說(shuō)明符的對(duì)象類型的變量則會(huì)被 retian。正是由于 __block 在 ARC 和 MRC 下的巨大差異,我們?cè)趯?xiě)代碼時(shí)一定要區(qū)分清楚到底是 ARC 還是 MRC。
盡管 ARC 已經(jīng)如此普及,我們可能已經(jīng)可以不用去管 MRC 的東西,但要有點(diǎn)一定要明白,ARC 和 MRC 都是基于引用計(jì)數(shù)的內(nèi)存管理,其本質(zhì)上是一個(gè)東西,只不過(guò) ARC 在編譯期自動(dòng)化的做了內(nèi)存引用計(jì)數(shù)的管理,使得系統(tǒng)可以在適當(dāng)?shù)臅r(shí)候保留內(nèi)存,適當(dāng)?shù)臅r(shí)候釋放內(nèi)存。
循環(huán)引用到此為止,東西并不多。如果明白了之前的知識(shí)點(diǎn),就會(huì)了解循環(huán)引用不過(guò)是前面知識(shí)點(diǎn)的自然延伸點(diǎn)罷了。
Copy 和 Release
在 ARC 下,有時(shí)需要手動(dòng)拷貝和釋放 block。在 MRC 下更是如此,可以直接用copy和release來(lái)拷貝和釋放
void(^blk_on_heap)(void)=[blk_on_stack copy];
[blk_on_heap release];
拷貝到堆后,就可以 用retain持有 block
[blk_on_heap retain];
然而如果 block 在棧上,使用retain是毫無(wú)效果的,因此推薦使用copy方法來(lái)持有 block。
block 是 C 語(yǔ)言的擴(kuò)展,所以可以在 C 中使用 block 的語(yǔ)法。比如,在上面的例子中,可以直接使用Block_copy和Block_release函數(shù)來(lái)代替copy和release方法
void(^blk_on_heap)(void)=Block_copy(blk_on_stack);
Block_release(blk_on_heap);
Block_copy的作用相當(dāng)于之前看到過(guò)的_Block_copy函數(shù),而且 Objective-C runtime 庫(kù)在運(yùn)行時(shí)拷貝 block 用的就是這個(gè)函數(shù)。同理,釋放 block 時(shí),runtime 調(diào)用了Block_release函數(shù)。
最后這里有一篇總結(jié) block 的文章的很不錯(cuò),推薦大家看看:http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/