iOS Block原理总结(二)

344 阅读4分钟

目录

1. block的类型

2. block的copy

3. block对象类型的auto变量的copy分析

4. __block的作用及原理


block的类型

  • block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
  1. NSGlobalBlock ( _NSConcreteGlobalBlock ) 全局block
  2. NSStackBlock ( _NSConcreteStackBlock )栈block
  3. NSMallocBlock ( _NSConcreteMallocBlock )堆block

三种block的内存分布如下:

截屏2021-03-26 下午4.12.31.png

不同类型的block区别和联系: 截屏2021-03-26 下午4.35.10.png

  • 每一种类型的block调用copy后的结果如下所示

image.png

block的copy

在实际开发场景中,我们使用block经常都会访问auto变量,所以在MRC的环境下我们要用到copy关键字,将栈block转换为堆block,避免栈block超出作用域被释放;

建议写法:

@property (copy, nonatomic) void (^block)(void);

ARC环境下,因为编译器内部会根据情况自动将栈上的block复制到堆上,copy和strong效果一样, 建议写法:

@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

ARC环境下,编译器内部会根据情况自动将栈上的block复制到堆上的几种情况:

  1. block作为函数返回值时
  2. 将block赋值给__strong指针时
  3. block作为Cocoa API中方法名含有usingBlock的方法参数时
  4. block作为GCD API的方法参数时

block对象类型的auto变量的copy分析

  • 先看看栈block对对象类型的auto变量的copy情况: 写一个person类,添加dealloc方法,当person释放时打印"Person - dealloc"

截屏2021-03-26 下午7.22.20.png

设置开启MRC环境: 截屏2021-03-26 下午7.23.59.png

看如下代码:

  • 定义了一个block,引用了一个peron的auto变量,之前也说到引用auto变量的block,如果没有copy,都是栈block。

MRC环境不会自动copy,所以下面的TestBlock是栈block

截屏2021-03-26 下午7.24.50.png

可以发现 block还没有释放,person就被释放了,说明block并没有强引用person(person默认是__strong类型)。

  • 把block进行copy操作,我们发现person并没有被释放,说明block强引用了person

截屏2021-03-26 下午7.29.50.png

我们再设置ARC模式下: 将person改为弱引用

截屏2021-03-26 下午7.35.52.png

发现person被释放了,说明block内部是弱引用了person

为了验证这个问题,我们转为C++看看内部实现:

截屏2021-03-26 下午7.52.31.png

可以看出来当person是 __weak 修饰则bolck内部的person指针修饰符是__weak

当person是默认修饰符(__strong)修饰时bolck内部的person修饰符是__strong

截屏2021-03-29 下午6.13.29.png

  • 当block被拷贝到堆上:
  1. 会调用block内部的copy函数
  2. copy函数内部会调用_Block_object_assign函数
  3. _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

截屏2021-03-29 下午6.14.51.png

  • block从堆上移除:
  1. 会调用block内部的dispose函数
  2. dispose函数内部会调用_Block_object_dispose函数
  3. _Block_object_dispose函数会自动释放引用的auto变量(release)

截屏2021-03-29 下午6.18.57.png

总结:

  • 栈block不会对auto变量产生强引用
  • 当栈block被copy到堆上,会根据变量的修饰符决定block对auto变量的引用关系,__weak则为弱引用,__strong则为强引用。

__block的作用及原理

  • 我们使用block时,有时候需要在block改变auto变量,但是通过前面熟悉的block的结构,对于auto变量,block内部会生成一个与其对应的变量,实际上并不能直接拿到外面的变量赋值,所以我们需要在变量前面加上__block,则可以进行修改啦。

那么__block里面到底做了什么呢,先看看下面的示例代码: 截屏2021-03-28下午8.43.15.png

转换为C++ 主要部分如下:

截屏2021-03-28下午8.50.24.png

可以发现被__block修饰后,里面会多生成一个__Block_byref_age_0的结构体,里面包含主要信息是一个指向自身的forwarding指针和变量的值,图示如下:

__Block_byref_age_0.png

总结一下:

  1. __block可以用于解决block内部无法修改auto变量值的问题
  2. __block不能修饰全局变量、静态变量(static)
  3. 编译器会将__block变量包装成一个对象,内部是一个与之对应的结构体,包含变量值和指向自身的forwarding指针
  • forwarding指针的作用: 主要是为了保证栈block被拷贝到堆上时,栈block和堆block中的变量能同步。
    1.当栈上的block被拷贝到堆上时,栈block的forwoarding指针会指向堆block
    2.从上面代码也能看出来(截图如下),访问__block变量是通过forwoarding去访问的,所以这时候无论是访问栈block还是堆block,最终取出的变量都是堆block中的变量。

截屏2021-03-29 下午6.33.40.png 截屏2021-03-29 下午6.30.31.png