一、什么是Block
- Block是将函数及其执行上下文封装的对象。
- __block_impl结构体内部中有isa,说明Block本质是OC对象。
二、Block变量截获
- 局部变量(对象)截获是值截获。比如:
NSInteger num = 1;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n) {
return n * num;
};
num = 2;
NSLog(@"%zd",block(3));
这里输出的是3,而不是2,原因就是对局部变量num的截获是值截获。
同样,在block里修改num也是无效的,甚至编译器会报错。
- 局部静态变量截获是指针截获。比如:
static NSInteger num = 1;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n) {
return n * num;
};
num = 2;
NSLog(@"%zd",block(3));
这里输出为6,意味着这里的num=2的修改是有效的,即是指针截获。
同样,在block里修改变量num也是有效的。
- 全局变量、静态全局变量截获:不截获,直接取值。
我们用clang编译看下结果:
static NSInteger num3 = 3;
NSInteger num4 = 4;
- (void)blockTest
{
NSInteger num = 1;
static NSInteger num2 = 2;
__block NSInteger num5 = 5;
void(^block)(void) = ^ {
NSLog(@"%zd",num);//局部变量
NSLog(@"%zd",num2);//静态变量
NSLog(@"%zd",num3);//全局变量
NSLog(@"%zd",num4);//全局静态变量
NSLog(@"%zd",num5);//__block修饰变量
};
block();
}
编译后:
- impl.isa = &_NSConcreteStackBlock; 即说明该 block 是栈block。
- 局部变量被编译成值形式,静态变量被编译成指针形式,全局变量并未截获。而__block修饰的变量也是以指针形式截获的,并且生成了一个新的结构体。
- 该对象有个属性num5,即我们用__block修饰的变量,这里__forwarding是指向自身的(栈block)。
- 一般情况下,如果我们要对block截获的局部变量进行赋值操作需添加__block修饰符,而对全局变量、静态变量是不需要添加__block修饰符的。另外block里去访问self或成员变量都会去截获self。
三、Block的几种形式
- 分为全局 Block(_NSConcreteGlobalBlock)、栈 Block(_NSConcreteStackBlock)、堆 Block(_NSConcreteMallocBlock)三种形式。
- 其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区。
- 不使用外部变量的Block是全局Block。
NSLog(@"%@",[^{
NSLog(@"globalBlock");
} class]);
输出:
__NSGlobalBlock__
- 使用外部变量并且未进行copy操作的Block是栈Block。
NSInteger num = 1;
NSLog(@"%@",[^{
NSLog(@"statcBlock:%zd",num);
} class]);
输出:
__NSStackBlock__
- 对栈Block进行copy操作就是堆Block,而对全局Block进行copy还是全局Block。
- 比如堆1中的全局进行copy操作,即赋值:
void(^globalBlock)(void) = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",[globalBlock class]);
输出:
__NSGlobalBlock__
仍然是全局
- 对2中的栈Block进行copy操作,即赋值:
void(^mallocBlock)(void) = ^{
NSLog(@"statcBlock:%zd",num);
};
NSLog(@"%@",mallocBlock);
输出:
__NSMallocBlock__
即如果对栈Block进行copy,将会copy到堆上;对堆Block进行copy,将会增加引用计数;对全局Block进行copy,因为是已经初始化的,所以什么也不做。
另外,__block变量在copy时,由于 __forwarding的存在,栈上的__forwarding指针会指向堆上的__forwarding变量,而堆上的__forwarding指针指向其自身,所以,如果是对__block的修改,其实是在修改堆上__block的变量。
即__forwarding指针存在的意义是,无论在任何内存位置,都可以顺利的访问同一个__block变量。
另外由于block捕获的__block修饰的变量会去持有变量,那么如果用__block去修饰self,self持有block,并且block内部使用__block修饰的self时,就会造成循环引用,即self持有block,block持有__block变量,__block变量持有self,造成内存泄漏。
比如
__block typeof (self) weakSelf = self;
_testBlock = ^{
NSLog(@"%@",weakSelf);
};
_testBlock();
如果要解决这种循环引用,可以主动的断开__block变量对self的持有,即在block内部执行完weakSelf后,将其置为nil。但这种方法有个问题,如果block一直不被调用,那么循环引用将一直存在。所以,我们最好使用__weak来修饰self。