編譯器版本為Clang5穿剖,主要技術(shù)是ARC,參考來自Objective-C Automatic Reference Counting (ARC)
block根據(jù)分配的內(nèi)存位置分為棧塊卦溢,堆塊糊余,全局塊。
一個(gè)block在創(chuàng)建的時(shí)候既绕,就可以確定其類型啄刹,判斷一個(gè)block是那種類型其實(shí)很簡(jiǎn)單,規(guī)則如下(MRC和ARC都適用):
1.如果一個(gè)block中引用了全局變量凄贩,或者沒有引用任何外部變量(屬性誓军、實(shí)例變量、局部變量)疲扎,那么該block為全局塊昵时。
2.其它引用情況(局部變量,實(shí)例變量椒丧,屬性)為棧塊壹甥。
上面的規(guī)則中并不存在堆塊的判斷,因?yàn)橐陨弦?guī)則的前提條件是:block在創(chuàng)建的時(shí)候壶熏,不是創(chuàng)建完之后賦值給變量句柠,代碼如下:
@interface MGBlockExample()
//屬性
@property(nonatomic,assign)int a;
@end
@implementation MGBlockExample{
int _b; //實(shí)例變量
}
//全局變量
int c = 3;
- (void)test{
/***** 全局塊 *****/
//引用全局變量
NSLog(@"aBlock:%@", ^{ NSLog(@"c:%d",c); }); //aBlock:__NSGlobalBlock__
//沒有引用變量
NSLog(@"bBlock:%@", ^{ NSLog(@""); }); //bBlock:__NSGlobalBlock__
/***** 棧塊 *****/
int d = 4;
//不給賦值,但引用了局部變量
NSLog(@"cBlock:%@",^void{ NSLog(@"d:%d",d); }); //cBlock:__NSStackBlock__
//不給賦值棒假,但引用了實(shí)例變量
NSLog(@"dBlock:%@",^void{ NSLog(@"_b:%d",_b); }); //dBlock:__NSStackBlock__
//不給賦值溯职,但引用了屬性
NSLog(@"eBlock:%@",^void{ NSLog(@"self.a:%d",self.a); }); //eBlock:__NSStackBlock__
}
@end
全局塊和棧塊已經(jīng)有規(guī)則可以判斷,并且適用于MRC和ARC帽哑,規(guī)則的前提條件是block創(chuàng)建的時(shí)候谜酒。那么在將創(chuàng)建完的塊之后賦值給變量會(huì)出現(xiàn)什么情況呢?看下面代碼:
- (void)test1{
//局部變量
void (^fBlock)() = ^(){
NSLog(@"d:%d",d);
};
//實(shí)例變量
void (^gBlock)() = ^(){
NSLog(@"_b:%d",_b);
};
//屬性
void (^hBlock)() = ^(){
NSLog(@"self.a:%d",self.a);
};
NSLog(@"fBlock:%@", fBlock);
NSLog(@"gBlock:%@", gBlock);
NSLog(@"hBlock:%@", hBlock);
//MRC:上面操作輸出//XBlock:__NSStackBlock__
//ARC:上面操作輸出//XBlock: __NSMallocBlock__
}
從上面代碼可以看到在MRC環(huán)境中妻枕,block引用局部變量僻族、實(shí)例變量、屬性的的結(jié)果依然是生成棧塊屡谐。而在ARC環(huán)境中述么,生成的是堆塊。原因如下:
block是retainable object pointer類型愕掏。
在ARC環(huán)境中度秘,ARC會(huì)對(duì)retainable type
進(jìn)行內(nèi)存管理:
If an object is declared with retainable object owner type, but without an explicit ownership qualifier, its type is implicitly adjusted to have __strong qualification.
如果一個(gè)被定義對(duì)象是retainable object owner類型,并且沒有明確指定內(nèi)存管理策略亭珍,那么編譯器會(huì)默認(rèn)為其添加__strong修飾詞敷钾。
所以上面的fBlock
在ARC環(huán)境中是這樣的:
__strong void (^fBlock)() = ^(){
NSLog(@"d:%d",d);
};
然后document又說:
With the exception of retains done as part of initializing a __strong parameter variable or reading a __weak variable, whenever these semantics call for retaining a value of block-pointer type, it has the effect of a Block_copy. The optimizer may remove such copies when it sees that the result is used only as an argument to a call.
對(duì)于block來說,不管是在創(chuàng)建對(duì)象的時(shí)候strong修飾還是在讀取weak修飾的變量肄梨,其效果和用Block_copy修飾是一樣的阻荒。當(dāng)編譯器覺得可以優(yōu)化的時(shí)候就不會(huì)執(zhí)行Block_copy函數(shù),例如一個(gè)block初始化完之后就被當(dāng)做一個(gè)參數(shù)使用了(例如cBlock众羡、dBlock侨赡、eBlock)
所以上面fBlock
的代碼轉(zhuǎn)成MRC是類似這樣的:
void (^fBlock)() = [^(){
NSLog(@"d:%d",d);
} copy];
給block對(duì)象的-copy
方法發(fā)送消息,會(huì)將該對(duì)象拷貝一份放入堆中粱侣,所以這個(gè)時(shí)候塊從棧塊變成堆塊羊壹。
補(bǔ)充一下block的內(nèi)存管理
不管是對(duì)block進(jìn)行retian
、copy
齐婴、release
,block的引用計(jì)數(shù)都不會(huì)增加油猫,始終為1。
NSGlobalBlock:使用retain
柠偶、copy
情妖、release
都無效,block依舊存在全局區(qū)诱担,且沒有釋放, 使用copy
和retian
只是返回block的指針毡证。
NSStackBlock:使用retain
、release
操作無效蔫仙;棧區(qū)block會(huì)在方法返回后將block空間回收料睛; 使用copy
將棧區(qū)block復(fù)制到堆區(qū),可以長(zhǎng)久保留block的空間摇邦,以供后面的程序使用恤煞。
NSMallocBlock:支持retian
、release
涎嚼,雖然block的引用計(jì)數(shù)始終為1阱州,但內(nèi)存中還是會(huì)對(duì)引用進(jìn)行管理,使用retain
引用+1法梯, release
引用-1苔货; 對(duì)于NSMallocBlock使用copy
之后不會(huì)產(chǎn)生新的block,只是增加了一次引用立哑,類似于使用retian
夜惭。
總結(jié)起來是這樣的:
1.在創(chuàng)建block的時(shí)候(MRC和ARC通用):
1.1. 如果一個(gè)block中引用了全局變量,或者沒有引用任何外部變量(屬性铛绰、實(shí)例變量诈茧、局部變量),那么該block為全局塊捂掰。
1.2. 其它引用情況(局部變量敢会,實(shí)例變量曾沈,屬性)為棧塊。
2.在將block對(duì)象賦值給其它對(duì)象^(oBlock)()
的時(shí)候(ARC):
2.1. 如果block是棧塊鸥昏,那么oBlock變成堆塊(因?yàn)镃lang編譯器幫我們往block發(fā)送了copy消息)塞俱。
2.2. 如果block是全局塊,那么oBlock也是全局塊吏垮,如果block是堆塊障涯,那么oBlock也是堆塊。
2.3. 如果僅把創(chuàng)建完的 block 當(dāng)做方法參數(shù)使用(不進(jìn)行其它賦值引用)膳汪,那么 block 的類型不變(參照1.1和1.2的情況)唯蝶。
3.在將block對(duì)象賦值給其它對(duì)象^(oBlock)()
的時(shí)候(MRC):
3.1. block是什么,oBlock便是什么遗嗽。