一、Block是什么?
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装函数及其上下文的OC对象
- block是封装了函数调用以及函数调用环境的OC对象
二、Block有几种类型
一、NSGlobalBlock:就是全局数据区的Block对象
那么什么时候block的类型是_NSConcreteGlobalBlock类型?
- 不捕获全局变量(包括全局静态变量)
- 没有捕获变量或者捕获的只有局部静态变量
- 以指针形式截获局部静态变量
二、NSMallocBlock
- 堆block(NSMallocBlock):在堆上创建的Block对象
在ARC情况下,编译器会根据情况自动将栈上的block复制到堆上,那么几种情况的Block的类型进行copy?
-
匿名函数返回赋值给block变量
-
将block赋值给__strong指针时
-
block作为Cocoa API中方法名含有usingBlock的方法参数时
-
block作为GCD API的方法参数时
大多数情况都是block作为函数返回值时进行copy操作。怎么判定是block类型是NSMallocBlock?
- 对匿名的block进行copy(匿名函数block赋给block变量是一种情况)
- 捕获到成员变量或者是__block声明的局部成员变量至少一种
三、栈block(NSStackBlock)
- 栈block(NSStackBlock): 在堆上创建的Block对象
栈上的block随时会被销毁,受系统控制。怎么声明NSStackBlock类型?
- 匿名声明的block
- 捕获局部变量或者__block局部变量至少一种
- 没有进行copy操作
三、对着三种类型block进行copy操作后的结果?
-
NSGlobalBlock 调用copy操作后,什么也不做
-
NSMallocBlock 调用copy操作后,复制效果是:引用计数增加;副本存储位置是堆
-
NSStackBlock 调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆
四、对应的block相关面试题
一、局部变量截获 是值截获
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%zd",block(2));
这里的输出是6而不是2,原因就是对局部变量num的截获是值截获。
同样,在block里如果修改变量num,也是无效的,甚至编译器会报错。
NSMutableArray * arr = [NSMutableArray arrayWithObjects:@"1",@"2", nil];
void(^block)(void) = ^{
NSLog(@"%@",arr);//局部变量
[arr addObject:@"4"];
};
[arr addObject:@"3"];
arr = nil;
block();
打印为1,2,3
局部对象变量也是一样,截获的是值,而不是指针,在外部将其置为nil,对block没有影响,而该对象调用方法会影响。
二、局部静态变量截获 是指针截获。
static NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%zd",block(2));
打印为:2 而不是6
因为是指针截获,在block里去修改变量m,也是有效的。
三、全局变量,静态全局变量截获:不截获,直接取值。
我们看一下各种数据类型clang编译结果:
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);//静态变量
NSLog(@"%zd",num3);//全局变量
NSLog(@"%zd",num4);//全局静态变量
NSLog(@"%zd",num5);//__block修饰变量
};
block();
}
编译成CPP文件后:
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0* Desc;
NSInteger num;//局部变量
NSInteger *num2;//静态变量
__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;
}
};
其中WYTest是文件名,blockTest是方法名,这些可以忽略。
_block_impl_0结构体中:
-
局部变量被编译成值形式
-
局部静态变量被编成指针形式
-
全局变量(全局静态变量、全局变量)并未截获
-
__block修饰的变量也是以指针形式截获的,并且指针指向生成了一个新的结构体对象:
struct __Block_byref_num5_0 {
void *__isa;
__Block_byref_num5_0 *__forwarding;
int __flags;
int __size;
NSInteger num5;
};
从这个结构体中表明:
- 保存int block_var变量
- 有一个指向__Block_byref_count_0实例的指针__forwarding。
- 这里__forwarding是指向自身的(栈block)
为什么要通过__forwarding指针完成对count变量的读写修改?
-
这样就保证无论是在栈上还是在堆上,都能通过都__forwarding指针找到在堆上创建的__Block_byref_num5_0结构体,通过结构内的__forwarding指针以完成对num5访问和修改
-
__Block_byref_num5_0 *num5是指向结构体的指针
下面通过代码来验证一下:
__block int num5 = 10;
NSLog(@"block捕获前地址:%p",&block_var);
void (^myblock)(void) = ^{
NSLog(@"num:%d", num5);
};
NSLog(@"block捕获后地址:%p",&num5);
num5 = 20;
myblock();
打印的结果表明捕获前后的地址是变化的并且值改变了
block捕获前地址:0x7ffeefbff4e8
block捕获后地址:0x102968608
num5:20
通过以上可以表明:
- __block声明的局部变量就变成__Block_byref_num5_0结构体对象
- 在捕获前在栈上,捕获后相当于把__block 变量copy到堆上
- 捕获后,copy到堆的结构体和捕获前栈的结构体都包含__forwarding指针都指向堆结构体__block变量的地址
网友评论