摘選自:[http://www.reibang.com/p/ee9756f3d5f6]
Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這個(gè)新功能“Blocks”快耿。從那開始囊陡,Block就出現(xiàn)在iOS和Mac系統(tǒng)各個(gè)API中,并被大家廣泛使用掀亥。一句話來形容Blocks撞反,帶有自動(dòng)變量(局部變量)的匿名函數(shù)。
Block在OC中的實(shí)現(xiàn)如下:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
從結(jié)構(gòu)圖中很容易看到isa搪花,所以O(shè)C處理Block是按照對(duì)象來處理的遏片。在iOS中,isa常見的就是_NSConcreteStackBlock撮竿,_NSConcreteMallocBlock丁稀,_NSConcreteGlobalBlock這3種(另外只在GC環(huán)境下還有3種使用的_NSConcreteFinalizingBlock,_NSConcreteAutoBlock倚聚,_NSConcreteWeakBlockVariable线衫,本文暫不談?wù)撨@3種,有興趣的看看官方文檔)
1.從捕獲外部變量的角度上來看
_NSConcreteStackBlock:
只用到外部局部變量惑折、成員屬性變量授账,且沒有強(qiáng)指針引用的block都是StackBlock。
StackBlock的生命周期由系統(tǒng)控制的惨驶,一旦返回之后白热,就被系統(tǒng)銷毀了。
_NSConcreteMallocBlock:
有強(qiáng)指針引用或copy修飾的成員屬性引用的block會(huì)被復(fù)制一份到堆中成為MallocBlock粗卜,沒有強(qiáng)指針引用即銷毀屋确,生命周期由程序員控制
_NSConcreteGlobalBlock:
沒有用到外界變量或只用到全局變量、靜態(tài)變量的block為_NSConcreteGlobalBlock,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束攻臀。
2.從持有對(duì)象的角度上來看:
- _NSConcreteStackBlock是不持有對(duì)象的焕数。
//以下是在MRC下執(zhí)行的
NSObject * obj = [[NSObject alloc]init];
NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
void (^myBlock)(void) = ^{
NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
};
NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
myBlock();
輸出:
1.Block外 obj = 1
2.Block外 obj = 1
Block中 obj = 1
- _NSConcreteMallocBlock是持有對(duì)象的。
//以下是在MRC下執(zhí)行的
NSObject * obj = [[NSObject alloc]init];
NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
void (^myBlock)(void) = [^{
NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
}copy];
NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
myBlock();
[myBlock release];
NSLog(@"3.Block外 obj = %lu",(unsigned long)obj.retainCount);
輸出:
1.Block外 obj = 1
2.Block外 obj = 2
Block中 obj = 2
3.Block外 obj = 1
- _NSConcreteGlobalBlock也不持有對(duì)象
//以下是在MRC下執(zhí)行的
void (^myBlock)(void) = ^{
NSObject * obj = [[NSObject alloc]init];
NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
};
myBlock();
輸出:
Block 中 obj = 1
由于_NSConcreteStackBlock所屬的變量域一旦結(jié)束刨啸,那么該Block就會(huì)被銷毀堡赔。在ARC環(huán)境下,編譯器會(huì)自動(dòng)的判斷设联,把Block自動(dòng)的從棧copy到堆善已。比如當(dāng)Block作為函數(shù)返回值的時(shí)候,肯定會(huì)copy到堆上离例。
1.手動(dòng)調(diào)用copy
2.Block是函數(shù)的返回值
3.Block被強(qiáng)引用换团,Block被賦值給__strong或者id類型
4.調(diào)用系統(tǒng)API入?yún)⒅泻衭singBlcok的方法
以上4種情況,系統(tǒng)都會(huì)默認(rèn)調(diào)用copy方法把Block賦復(fù)制
但是當(dāng)Block為函數(shù)參數(shù)的時(shí)候宫蛆,就需要我們手動(dòng)的copy一份到堆上了啥寇。這里除去系統(tǒng)的API我們不需要管,比如GCD等方法中本身帶usingBlock的方法洒扎,其他我們自定義的方法傳遞Block為參數(shù)的時(shí)候都需要手動(dòng)copy一份到堆上辑甜。
copy函數(shù)把Block從棧上拷貝到堆上,dispose函數(shù)是把堆上的函數(shù)在廢棄的時(shí)候銷毀掉袍冷。
Block中__block實(shí)現(xiàn)原理
1.普通非對(duì)象的變量
__block的變量也被轉(zhuǎn)化成了一個(gè)結(jié)構(gòu)體
2.對(duì)象的變量
在MRC環(huán)境下磷醋,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制胡诗。
而在ARC環(huán)境下邓线,對(duì)于聲明為__block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain煌恢,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象骇陈,所以才會(huì)產(chǎn)生循環(huán)引用的問題!
最后
關(guān)于Block捕獲外部變量有很多用途瑰抵,用途也很廣你雌,只有弄清了捕獲變量和持有的變量的概念以后,之后才能清楚的解決Block循環(huán)引用的問題二汛。
再次回到文章開頭婿崭,5種變量,自動(dòng)變量肴颊,函數(shù)參數(shù) 氓栈,靜態(tài)變量,靜態(tài)全局變量婿着,全局變量授瘦,如果嚴(yán)格的來說醋界,捕獲是必須在Block結(jié)構(gòu)體__main_block_impl_0里面有成員變量的話,Block能捕獲的變量就只有帶有自動(dòng)變量和靜態(tài)變量了提完。捕獲進(jìn)Block的對(duì)象會(huì)被Block持有形纺。
對(duì)于非對(duì)象的變量來說,
自動(dòng)變量的值氯葬,被copy進(jìn)了Block挡篓,不帶__block的自動(dòng)變量只能在里面被訪問婉陷,并不能改變值帚称。
帶__block的自動(dòng)變量 和 靜態(tài)變量 就是直接地址訪問。所以在Block里面可以直接改變變量的值秽澳。
而剩下的靜態(tài)全局變量闯睹,全局變量,函數(shù)參數(shù)担神,也是可以在直接在Block中改變變量值的楼吃,但是他們并沒有變成Block結(jié)構(gòu)體__main_block_impl_0的成員變量,因?yàn)樗麄兊淖饔糜虼笸叮钥梢灾苯痈乃麄兊闹怠?/p>
值得注意的是孩锡,靜態(tài)全局變量,全局變量亥贸,函數(shù)參數(shù)他們并不會(huì)被Block持有躬窜,也就是說不會(huì)增加retainCount值。
對(duì)于對(duì)象來說炕置,
在MRC環(huán)境下荣挨,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制朴摊。
而在ARC環(huán)境下默垄,對(duì)于聲明為__block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain甚纲,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象口锭。
ARC環(huán)境下,Block也是存在__NSStackBlock的時(shí)候的
在ARC環(huán)境下介杆,Block也是存在__NSStackBlock的時(shí)候的讹弯,平時(shí)見到最多的是_NSConcreteMallocBlock,是因?yàn)槲覀儠?huì)對(duì)Block有賦值操作这溅,所以ARC下组民,block 類型通過=進(jìn)行傳遞時(shí),會(huì)導(dǎo)致調(diào)用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈悲靴。并導(dǎo)致 NSStackBlock 類型的 block 轉(zhuǎn)換為 NSMallocBlock 類型臭胜。
舉例如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block int temp = 10;
NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});
return 0;
}
輸出
<__NSStackBlock__: 0x7fff5fbff768>