iOS面试题与核心基础之block

block本质

  • block本质上是一个OC对象(内部有个isa指针)
  • block是封装了函数调用以及函数调用环境的OC对象
block的底层结构

可以通过clang去编译成c++源码来验证

block的变量捕获

  1. 局部变量
    静态局部变量,捕获指针,即属于指针传递;
    auto的基本数据类型局部变量,捕获其值(直接拷贝值),属于值传递;
    auto的对象类型连同所有权修饰符(引用修饰符)一起捕获
  2. 全局变量
    不捕获,直接访问

block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • NSGlobalBlock ( _NSConcreteGlobalBlock )存在数据区
    没有捕获auto变量(局部非static变量),对其copy,什么都不做
  • NSStackBlock ( _NSConcreteStackBlock )存在栈区
    捕获auto变量,对其copy,会由栈复制到堆
  • NSMallocBlock ( _NSConcreteMallocBlock )存在堆区
    NSStackBlock复制而来的,对其copy,引用计数+1

block的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况

  • block作为函数返回值时
  • 将block赋值给__strong指针时
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD API的方法参数时
    在MRC环境下需要自己管理,自己实现copy才不会因为block退栈销毁导致的崩溃
// MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
// ARC下block属性也可以使用strong关键字
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

__block本质

编译器会将__block修饰的变量包装成一个对象;
当block在栈上时,forwarding是指向自身的指针;当block被拷贝到堆上时,栈上的forwarding指针会指向堆上的block,堆上的forwarding指针还是指向自身。这样不论访问的是栈上的指针还是堆上的指针最终都能访问到堆上的真正需要操作的变量。
使用 __block可以用于解决block内部无法修改auto变量值的问题。 __block不能修饰全局变量、静态变量(static)。全局变量能直接访问修改,而静态局部变量值指针访问,也能修改。

如何解决循环引用

  • __weak
__weak typeof(self) weakSelf = self;
self.block = ^{
    NSLog("%p",weakSelf);
}

使用上述方法时,block内部执行时间比较长,在执行时,self突然被释放了(例如self是控制器,控制器返回了),而block是在堆空间上,并不会被释放,当block内部继续访问self,这个时候会出现野指针, 也就是说weakSelf变成了nil,极有可能导致崩溃。
解决方案就是使用__strong在block内部对weakSelf进行强引用

__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
    NSLog("%p",strongSelf);
}

block引用的外部变量的是__weak修饰的weakSelf对象,
所以block初始化并copy到堆上,不会强引用self。
但是执行block的时候,其实是执行一个静态函数,
在执行的过程中,生成了strongSelf对象,这个时候,产生了闭环。
但是这个strongSelf在栈空间上,在函数执行结束后,strongSelf会被系统回收,此时闭环被打破。
内容来自:https://www.jianshu.com/p/24c7e8563c56,未验证

  • __unsafe_unretained
__unsafe_unretained id weakSelf = self;
self.block = ^{
    NSLog("%p",weakSelf);
}

缺点:weakSelf被释放之后指针不会被设置为nil,访问将引起崩溃

  • __block
__block id weakSelf = self;
self.block = ^{
    NSLog("%p",weakSelf);
    weakSelf = nil;
}

缺点:block必须执行之后weakSelf = nil,才能打破循环引用。

以上是ARC前提之下的解决方法,那么在MRC之下仍然可以使用__unsafe_unretained,但要注意weakSelf被释放的时机。MRC下__block修饰的变量,并不改变引用计数,同时block内部并不对引入的外部对象,更改引用计数。所以也可以使用__block来解决。

面试题

  1. 什么是block
    block是将函数及其执行上下文(调用环境)封装起来的对象。

  2. 下面代码的打印结果是什么?分析一下

int multiplier = 6;
int (^Block)(int) = ^int(int num) {
  return num * multiplier;
}
multiplier = 2;
NSLog(@"result is %d", Block(3));

局部基本数据类型,block直接诶捕获其值,后续值的修改对block已经捕获的值没影响。所以是结果是result is 18

  1. 什么场景下需要使用_block修饰符
    一般情况下,对捕获的 局部变量 进行赋值操作需要添加__block修饰符。(当且仅当对变量本身进行修改时需要添加,比如被捕获的变量是数组,对数组进行增删改数组元素则不需要,修改数组本身这个对象才需要添加。)对于静态局部变量、全局变量则不需要。静态局部变量通过指针访问,全局变量则是直接访问,都能做到修改其值。

  2. 下面代码的打印结果是什么?分析一下

__block int multiplier = 6;
int (^Block)(int) = ^int(int num) {
  return num * multiplier;
}
multiplier = 2;
NSLog(@"result is %d", Block(3));

编译器会将__block修饰的变量包装成一个对象;
multiplier = 2; => 编译之后变成 multiplier.__forwarding.multiplier = 2;
也就是说block执行之前能对multiplier的值修改成功,结果是6

  1. 下面的代码存在问题么?为什么?
__block MyBlockViewController* blockSelf = self
_myBlock = ^int(int num) {
     return num *blockSelf.multiplier
}
_myBlock(2);

ARC下会产生循环引用,MRC下则不会。
上述代码中,block被赋值给『_block』实例变量,block被复制到了堆上,而堆上的block会对__block修饰的变量产生强引用,也就是对self产生了间接强引用,self本身对『_block』实例变量是强引用故而导致了循环引用。解决方法是,在block内部使用完blockSelf之后释放掉。但是如果block一直不被执行的话,强引用就会一直存在。
MRC下,block自行管理,编译器不会将block'复制到堆上,而栈上的block并不会对__block变量产生强引用(因block也可能被随时释放),故而没有循环引用问题。

你可能感兴趣的:(iOS面试题与核心基础之block)