一,首先介紹什么是Block榜旦?它就是一個(gè)(里面存儲(chǔ)了 指向函數(shù)體中包含定義block時(shí)的代碼塊的函數(shù)指針,以及block外部上下文變量等信息的)結(jié)構(gòu)體咐旧。
二铣墨,在OC中伊约,有三種類型的Block:
1屡律,_NSConcreteGlobalBlock ?保存在全局區(qū)
2,_NSConcreteStackBlock ?保存在棧中
3降淮,_NSConcreteMallocBlock? 保存在堆中
在代碼中超埋,具體怎么判斷屬于哪種類型呢?請(qǐng)看下面demo
提示:該demo是在MRC的環(huán)境下運(yùn)行的佳鳖。
@implementation ViewController
- (void)viewDidLoad {
? ?[super viewDidLoad];
// 一
NSInteger i = 10;
void(^oneBlock)(void) = ^{
NSLog(@"%zd", i);
};
NSLog(@"%@", oneBlock);?
//打印結(jié)果:__NSStackBlock__
//二
void(^twoBlock)(void) = ^{
};
NSLog(@"%@",twoBlock);
//打印結(jié)果:__NSGlobalBlock__
//三
void(^threeBlock)(void) = [oneBlock copy];
NSLog(@"%@",threeBlock);
//打印結(jié)果:__NSMallocBlock__
}
@end
log信息:
2017-08-17 09:47:08.514 test[1008:27892] <__NSStackBlock__: 0xbff12f38>
2017-08-17 09:47:08.515 test[1008:27892] <__NSGlobalBlock__: 0xee060>
2017-08-17 09:47:08.515 test[1008:27892] <__NSMallocBlock__: 0x79789a30>
結(jié)果分析:
1霍殴,oneBlock使用了外部變量,地址顯示在棧區(qū)(MRC)系吩。
2来庭,twoBlock沒(méi)有使用外部變量,地址顯示在全局區(qū)(ARC和MRC一樣)穿挨。
由1和2得出結(jié)論月弛,如果block沒(méi)有訪問(wèn)外部變量,那么該block是NSGlobalBlock尊搬,如果訪問(wèn)了外部變量冀泻,那么該block是NSStackBlock肢专。
3,在對(duì)棧區(qū)oneBlock進(jìn)行copy之后,得到的threeBlock地址顯示在堆區(qū)苗傅。
由3得出結(jié)論:對(duì)棧區(qū)block進(jìn)行copy之后的得到的新block在堆區(qū)旱物。
其實(shí)在使用__block變量的Block從棧上復(fù)制到堆上時(shí),__block變量也被從棧復(fù)制到堆上并被Block所持有。
三府蔗,變量的復(fù)制
對(duì)于 block 外的變量引用不铆,block 默認(rèn)是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來(lái)實(shí)現(xiàn)訪問(wèn)的,如果對(duì)象是引用類型,則block會(huì)將其引用計(jì)數(shù)加一 ,如下圖所示:
對(duì)于用 __block 修飾的外部變量引用,block 是復(fù)制其引用地址來(lái)實(shí)現(xiàn)訪問(wèn)的茄袖,如下圖所示:
四,ARC對(duì)block的影響
在ARC模式下涯呻,在棧間傳遞block時(shí)胀滚,不需要手動(dòng)copy棧中的block剑刑,即可讓block正常工作媳纬。主要原因是ARC對(duì)棧中的block自動(dòng)執(zhí)行了copy钮惠,將_NSConcreteStackBlock類型的block轉(zhuǎn)換成了_NSConcreteMallocBlock的block预明。
@interface ViewController : UIViewController
@property (strong,nonatomic)void (^block)();
@property (weak,nonatomic)void (^weakBlock)();
@property (strong,nonatomic)void (^stackBlock)();
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//
NSInteger i = 10;
self.block = ^{
NSLog(@"%zd", i);
};
NSLog(@"%@", self.block);
//
self.weakBlock = ^{
NSLog(@"%zd", i);
};
NSLog(@"%@", self.weakBlock);
//
self.stackBlock = ^{};
NSLog(@"%@", self.stackBlock);
}
打印結(jié)果:
2017-08-17 13:24:32.088 test[1882:89514] <__NSMallocBlock__: 0x7ba2a200>
2017-08-17 13:24:32.088 test[1882:89514] <__NSStackBlock__: 0xbff3ef20>
2017-08-17 13:24:32.089 test[1882:89514] <__NSGlobalBlock__: 0xc2080>
結(jié)論:
1卤材,ARC會(huì)自動(dòng)幫strong類型且捕獲外部變量的block進(jìn)行copy。
2,ARC不會(huì)幫weak類型且捕獲外部變量的block進(jìn)行copy冰评。
此時(shí)weakBlock是在棧內(nèi)存上映胁,由于棧內(nèi)存有生命周期的約束,所以在運(yùn)行到touchesBegan方法的時(shí)候甲雅,weakBlock已經(jīng)被釋放了解孙,程序會(huì)奔潰。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.weakBlock();
}
重點(diǎn):研究block屬性用strong抛人,weak妆距,還是copy修飾的目的,就是保證block在使用的時(shí)候不會(huì)出現(xiàn)被釋放的情況函匕。
這里還有一點(diǎn)關(guān)于block類型的ARC屬性。上文也說(shuō)明了蚪黑,ARC會(huì)自動(dòng)幫strong類型且捕獲外部變量的block進(jìn)行copy盅惜,所以在定義block類型的屬性時(shí)也可以使用strong,不一定使用copy忌穿。也就是以下代碼:
/** 假如有棧block賦給以下兩個(gè)屬性 **/
// 這里因?yàn)锳RC抒寂,當(dāng)棧block中會(huì)捕獲外部變量時(shí),這個(gè)block會(huì)被copy進(jìn)堆中
// 如果沒(méi)有捕獲外部變量掠剑,這個(gè)block會(huì)變?yōu)槿诸愋?/p>
// 不管怎么樣屈芜,它都脫離了棧生命周期的約束
// 這里都會(huì)被copy進(jìn)堆中
@property (strong, nonatomic) Block *strongBlock;
@property (copy, nonatomic) Block *copyBlock;
五:在開(kāi)發(fā)中的應(yīng)用場(chǎng)景
1,block作為類的屬性朴译,在需要的時(shí)候去調(diào)用井佑。
2,block作為方法的參數(shù)使用眠寿,在函數(shù)的內(nèi)部去調(diào)用躬翁,由外界去定義。
3盯拱,block作為方法的返回值使用盒发,在函數(shù)的內(nèi)部去定義,由外界去調(diào)用狡逢,目的是替代方法宁舰。
項(xiàng)目中注意事項(xiàng):1,循環(huán)引用的問(wèn)題奢浑。2蛮艰,聲明block屬性的修飾符問(wèn)題。3殷费,block訪問(wèn)外部變量的問(wèn)題印荔。
參考文章:www.cocoachina.com/ios/20161025/17198.html