Block相关

184 阅读3分钟

Block数据结构

struct __ArcClass__TestArc_block_impl_0 {
  struct __block_impl impl;
  struct __ArcClass__TestArc_block_desc_0* Desc;
  int age;
  __ArcClass__TestArc_block_impl_0(void *fp, struct __ArcClass__TestArc_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • struct __block_impl impl
struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
};       

定义一个block的时候,底层生成了一个代表block的结构体__ArcClass__TestArc_block_impl_0,该结构体有一个__block_impl类型的impl成员变量和代表捕获变量的成员变量。其中impl的isa 代表了block的类型,FuncPtr代表了block的实际调用方法,该方法的参数为__ArcClass__TestArc_block_impl_0。

截获变量

  • static修饰的变量, 在block内可以修改变量的值,因为在底层block捕获的是地址
  • 全局变量可以直接在block中修改值,block不会捕获全局变量, 而是直接使用, 所以可以直接改值
  • auto变量需要__block修饰才能修改

img

Block的内存管理

Block的类型

Block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型 NSGlobalBlock 全局 block 没有访问auto变量 NSStackBlock 栈 block 访问了auto变量 NSMallocBlock 堆 block __NSStackBlock__调用了copy

image.png

Block copy

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

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

Block 变量的内存管理

  • 基本数据类型的auto变量,当栈上block复制到堆上时, 会直接将捕获的基本数据类型变量复制到堆中
  • 对象类型的auto变量,当变量被强引用修饰时, block复制到堆上的过程中会调用copy函数, copy函数内部会调用里面的_Block_object_assign函数, 对被捕获的对象变量进行强引用
  • 对象类型的auto变量,当对象变量被__weak修饰时, block从栈中复制到堆中, 依然会调用copy函数, copy函数内部会调用里面的_Block_object_assign函数,只不过不会再对被__weak修饰的变量进行强引用
  • __block修饰的auto变量,在底层会被包装成一个__Block_byref_age_0对象,当block从栈上复制到堆上时, 就会调用__main_block_desc_0中的copy函数, 对__Block_byref_age_0对象进行强引用,移除时会调用dispose函数,移除强引用。

Block的循环引用

  • __weak: 当person被释放时, block中的__weak person会指向nil
  • __unsafe_unretained: 当person被释放时, block中的__unsafe_unretained person不会指向nil, 造成野指针
  • __block: 手动设置nil

__block修饰符

可以用于解决block内部无法修改auto变量值的问题,编译器会将block变量包装成一个对象。__block不能修饰全局变量、静态变量。

__block结构

对于__block变量,底层会封装成一个对象,其中通过__forwarding指向自己,来访问真实的变量。

image.png

__forwarding指针

  • 当block在栈上时, 通过__forwarding指针拿到的是栈中的__block结构体
  • 当block在堆上时, 通过__forwarding指针拿到的是堆中的__block结构体

为什么要通过__forwarding访问?

这是因为,如果__block变量在栈上,就可以直接访问,但是如果已经拷贝到了堆上,访问的时候,还去访问栈上的,就会出问题,所以,先根据__forwarding找到堆上的地址,然后再取值

__block内存管理

  • 当Block在栈上时,并不会对__block变量产生强引用。

  • 当Block被copy到堆时,会调用Block内部的copy函数,copy函数内部会调用_Block_object_assign函数, _Block_object_assign函数会对__block变量形成强引用(retain)。

  • 当Block从堆中移除时,会调用Block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数\,_Block_object_dispose函数会自动释放引用的__block变量(release)

image.png