block实质

  • block实质上是一个OC目标(内部有个isa指针)
  • block是封装了函数调用以及函数调用环境的OC目标

能够经过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会被体系回收,此刻闭环被打破。

  • __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也或许被随时开释),故而没有循环引证问题。