iOS BAT 关于Block的面试题
我先附上一篇我觉得关于 block 源码分析的还不错的博文
关于 block 的面试问题无外乎就是下面五点,牢牢掌握面试就再也不怕了,block 之所以在面试中难,就是因为细节太多了
- 什么是 block
- block 的截获变量特性?
- __block 的理解
- 栈上 block 和堆上 block 的区别?在 MRC和 ARC下有什么需要注意的?
- 常见循环引用
什么是 block?
block 就是将函数执行上下文封装起来的对象
block的本质就是对象,其内部数据结构如下
block 的截获变量特性?
- 基本数据类型的局部变量 Block 可以截获其值
- 对于对象类型的局部变量连同所有权修饰符一起截获
- 局部静态变量以指针的形式进行截获
- 全局变量和静态全局变量,block 是不截获的
为什么要理解 block 截获变量特性呢? 下面我举个小例子,你就清楚了
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int m = 6;
int(^Block)(int) = ^int(int num) {
return num * m;
};
NSLog(@"%d",Block(2));
}
return 0;
}
上述代码输出结果想必大家都知道了,12 嘛。如果我们将代码改进一下,如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int m = 6;
int(^Block)(int) = ^int(int num) {
return num * m;
};
m = 8;
NSLog(@"%d",Block(2));
}
return 0;
}
这个时候你再看,结果是啥呢?我们在 xcode 中运行一下,结果还是 12,为什么呢? 我们看上面截获变量特性就能理解了,局部基本数据类型,截获其值,什么是截获,就是在 block 执行体内部,编译器会对 m 的值进行 copy 操作,所以无论你下面 m 如何修改,其结果是不变的。
如果是静态局部变量 ’static int m = 6‘, 截获指针,那么上述代码的结果就是 16,因为指针指向的是该数据的内存地址,我们通过内部地址访问该值,所以下面修改,访问到的值也是最终修改的值。
全局变量是不截获的,所以你外部如何修改,在 block 最终调用的时候都是你修改后的值。
__block 的理解
通过__block 修饰的变量最终会经系统编译成为一个结构体,结构体中有一个 isa 指针,和一个值 m 以及 __forwarding 指针,经过修饰以后,我们调用赋值操作其实就会转变为 m._forwarding->m = x,而在 block 内部也会对该__block 对象进行拷贝,故通过__block可以修改被截获变量的值
全局 Block
当我们声明一个block时,如果这个block没有捕获外部的变量,那么这个block就位于全局区,此时对NSGlobalBlock的retain、copy、release操作都无效。ARC和MRC环境下都是如此。
栈 Block
这里可能有人会问,平时编程的时候很少遇到位于栈区的block,为什么呢?因为在ARC环境下,当我们声明并且定义了一个block,并且没有为Block添加额外的修饰符(默认是__strong修饰符),如果该Block捕获了外部的变量,实质上是有一个从__NSStackBlock__转变到__NSMallocBlock__的过程,只不过是系统帮我们完成了copy操作,将栈区的block迁移到堆区,延长了Block的生命周期。对于栈区block而言,栈block在当函数退出的时候,该空间就会被回收。
那什么时候在ARC的环境下出现__NSStackBlock__呢?如果我们在声明一个block的时候,使用了__weak或者__unsafe__unretained的修饰符,那么系统就不会为我们做copy的操作,不会将其迁移到堆区。
堆 Block
在MRC环境下,我们需要手动调用copy方法才可以将block迁移到堆区,而在ARC环境下,__strong修饰的(默认)block只要捕获了外部变量就会位于堆区,NSMallocBlock支持retain、release,会对其引用计数+1或 -1。声明以及定义位于堆区的block如上图所示。
常见循环引用
block 自循坏引用问题
代码如下
_array = [NSMutableArray arrayWithObject:@"testBlock"];
_strBlk = ^NSString*(NSString *num) {
return [NSString stringWithFormat:@"hello %@", _array.firstObject];
};
_strBlk(@"hello");
分析如下图
解决方案,使用__weak 对 array 进行修饰,代码如下:
_array = [NSMutableArray arrayWithObject:@"testBlock"];
__weak NSMutableArray *weakArray = _array;
_strBlk = ^NSString*(NSString *num) {
return [NSString stringWithFormat:@"hello %@", weakArray.firstObject];
};
_strBlk(@"hello");
block 大循坏引用问题
__block BlockObject *objc = self;
_blk = ^int(int age) {
int res = age * objc.num;
return res;
};
_blk(3);
分析如下:
_block 变量强持有对象,对象持有block,block 又持有了__block 的变量,故产生了大环循环引用
解决方案,咱们使用断环的方式去解决,如图右
代码如下
__block BlockObject *objc = self;
_blk = ^int(int age) {
int res = age * objc.num;
objc = nil;
return res;
};
_blk(3);
这样解决有一个小弊端,如果我们这个 blk block 永远没有不被调用,那么循环引用就一直存在。
暂时就这么多吧,水文一篇