什么是Block?
是將函數(shù)及其執(zhí)行上下文(環(huán)境)封裝起來(lái)的oc對(duì)象。
NSInteger num = 3;
? ? NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
? ? return n*num; ? ?};
? ? block(2);
block內(nèi)部有isa指針,所以說(shuō)其本質(zhì)也是OC對(duì)象。
block內(nèi)部則為:
static NSInteger __WYTest__blockTest_block_func_0(struct __WYTest__blockTest_block_impl_0 *__cself, NSInteger n) {
? NSInteger num = __cself->num; // bound by copy
? ? ? ? return n*num;
? ? }
所以說(shuō) Block是將函數(shù)及其執(zhí)行上下文封裝起來(lái)的對(duì)象,既然block內(nèi)部封裝了函數(shù),那么它同樣也有參數(shù)和返回值。
__block_impl結(jié)構(gòu)體為
struct __block_impl {
? void *isa;//isa指針,所以說(shuō)Block是對(duì)象
? int Flags;
? int Reserved;
? void *FuncPtr;//函數(shù)指針
};
Block變量截獲
1.局部變量截獲是值截獲
eg:NSInteger num = 3;
? ? ? ? NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
? ? ? ? return n*num;
? ? };
? ? num = 1;
? ? NSLog(@"%zd",block(2));
這里的輸出是6而不是2,原因就是對(duì)局部變量num的截獲是值截獲。
同樣,在block里如果修改變量num,也是無(wú)效的,甚至編譯器會(huì)報(bào)錯(cuò)。
NSMutableArray * arr = [NSMutableArray arrayWithObjects:@"1",@"2", nil];
? ? void(^block)(void) = ^{
? ? ? ? NSLog(@"%@",arr);//局部變量
? ? ? ? [arr addObject:@"4"];
? ? };
? ? [arr addObject:@"3"];
? ? arr = nil;
? ? block();
打印為1,2,3
局部對(duì)象變量也是一樣,截獲的是值,而不是指針,在外部將其置為nil,對(duì)block沒(méi)有影響,而該對(duì)象調(diào)用方法會(huì)影響
2、局部靜態(tài)變量截獲 是指針截獲。
static NSInteger num = 3;
? ? NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
? ? ? ? return n*num;
? ? };
? ? num = 1;
? ? NSLog(@"%zd",block(2));
輸出為2,意味著num = 1這里的修改num值是有效的,即是指針截獲。
同樣,在block里去修改變量m,也是有效的。
3、全局變量,靜態(tài)全局變量截獲:不截獲,直接取值。
static NSInteger num3 = 300;
NSInteger num4 = 3000;
- (void)blockTest{
? ? NSInteger num = 30;
? ? static NSInteger num2 = 3;
? ? __block NSInteger num5 = 30000;
? ? void(^block)(void) = ^{
? ? ? ? NSLog(@"%zd",num);//局部變量
? ? ? ? NSLog(@"%zd",num2);//靜態(tài)變量
? ? ? ? NSLog(@"%zd",num3);//全局變量
? ? ? ? NSLog(@"%zd",num4);//全局靜態(tài)變量
? ? ? ? NSLog(@"%zd",num5);//__block修飾變量
? ? }; ? ?block();
}
編譯后
? struct __WYTest__blockTest_block_impl_0 {
? struct __block_impl impl;
? struct __WYTest__blockTest_block_desc_0* Desc;
? ?NSInteger num;//局部變量
? ?NSInteger *num2;//靜態(tài)變量
? __Block_byref_num5_0 *num5; // by ref//__block修飾變量
? __WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) {
? ? impl.isa = &_NSConcreteStackBlock;
? ? impl.Flags = flags;
? ? impl.FuncPtr = fp;
? ? Desc = desc;
? }
};
( impl.isa = &_NSConcreteStackBlock;這里注意到這一句,即說(shuō)明該block是棧block)
可以看到局部變量被編譯成值形式,而靜態(tài)變量被編成指針形式,全局變量并未截獲。而__block修飾的變量也是以指針形式截獲的,并且生成了一個(gè)新的結(jié)構(gòu)體對(duì)象:
struct __Block_byref_num5_0 {
?void *__isa;
?__Block_byref_num5_0 *__forwarding;
?int __flags;
?int __size;
?NSInteger num5;
?};
該對(duì)象有個(gè)屬性:num5,即我們用__block修飾的變量。
這里__forwarding是指向自身的(棧block)。
一般情況下,如果我們要對(duì)block截獲的局部變量進(jìn)行賦值操作需添加__block修飾符,而對(duì)全局變量,靜態(tài)變量是不需要添加__block修飾符的。
另外,block里訪問(wèn)self或成員變量都會(huì)去截獲self。
Block的幾種形式
分為全局Block(_NSConcreteGlobalBlock)、棧Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三種形式
其中棧Block存儲(chǔ)在棧(stack)區(qū),堆Block存儲(chǔ)在堆(heap)區(qū),全局Block存儲(chǔ)在已初始化數(shù)據(jù)(.data)區(qū)
1、不使用外部變量的block是全局block
eg:NSLog(@"%@",[^{
? ? ? ? NSLog(@"globalBlock");
? ? } class]);
輸出:__NSGlobalBlock__
2、使用外部變量并且未進(jìn)行copy操作的block是棧block
eg: NSInteger num = 10;
? ? NSLog(@"%@",[^{
? ? ? ? NSLog(@"stackBlock:%zd",num);
? ? } class]);
輸出:__NSStackBlock__
日常開(kāi)發(fā)常用于這種情況
[self testWithBlock:^{
? ? NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block {
? ? block();
? ? NSLog(@"%@",[block class]);
}
3、對(duì)棧block進(jìn)行copy操作,就是堆block,而對(duì)全局block進(jìn)行copy,仍是全局block
比如堆1中的全局進(jìn)行copy操作,即賦值:
void (^globalBlock)(void) = ^{
? ? ? ? NSLog(@"globalBlock");
? ? };
NSLog(@"%@",[globalBlock class]);
輸出:__NSGlobalBlock__
而對(duì)2中的棧block進(jìn)行賦值操作:
NSInteger num = 10;
void (^mallocBlock)(void) = ^{
? ? ? ? NSLog(@"stackBlock:%zd",num);
? ? };
NSLog(@"%@",[mallocBlock class]);
輸出:__NSMallocBlock__
對(duì)棧blockcopy之后,并不代表著棧block就消失了,左邊的mallock是堆block,右邊被copy的仍是棧block
[self testWithBlock:^{
? ? NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block{
? ? block();
? ? dispatch_block_t tempBlock = block;
? ? NSLog(@"%@,%@",[block class],[tempBlock class]);
}
輸出:__NSStackBlock__,__NSMallocBlock__
即如果對(duì)棧Block進(jìn)行copy,將會(huì)copy到堆區(qū),對(duì)堆Block進(jìn)行copy,將會(huì)增加引用計(jì)數(shù),對(duì)全局Block進(jìn)行copy,因?yàn)槭且呀?jīng)初始化的,所以什么也不做。
另外,__block變量在copy時(shí),由于__forwarding的存在,棧上的__forwarding指針會(huì)指向堆上的__forwarding變量,而堆上的__forwarding指針指向其自身,所以,如果對(duì)__block的修改,實(shí)際上是在修改堆上的__block變量。
即__forwarding指針存在的意義就是,無(wú)論在任何內(nèi)存位置, 都可以順利地訪問(wèn)同一個(gè)__block變量。
另外由于block捕獲的__block修飾的變量會(huì)去持有變量,那么如果用__block修飾self,且self持有block,并且block內(nèi)部使用到__block修飾的self時(shí),就會(huì)造成多循環(huán)引用,即self持有block,block 持有__block變量,而__block變量持有self,造成內(nèi)存泄漏。
比如:?
?__block typeof(self) weakSelf = self;
? ? _testBlock = ^{
? ? ? ? NSLog(@"%@",weakSelf);
? ? };
? ? _testBlock();
如果要解決這種循環(huán)引用,可以主動(dòng)斷開(kāi)__block變量對(duì)self的持有,即在block內(nèi)部使用完weakself后,將其置為nil,但這種方式有個(gè)問(wèn)題,如果block一直不被調(diào)用,那么循環(huán)引用將一直存在。
所以,我們最好還是用__weak來(lái)修飾self