block

作者: 张_何 | 来源:发表于2019-12-11 17:44 被阅读0次

block本质

  • block 本质上是一个 oc对象,它内部也有 isa 指针
  • block 是封装了函数调用以及函数调用环境的 OC 对象
  • block 的底层结构:
int age = 20;
void (^block)(void) = ^{
  NSLog(@"age is %d", age);
}
// 对于上面的代码转换成 c++后结构如下:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0  * Desc;  // block 的描述信息
  int age;  // 保存捕获的外部变量 age 的值
}  
 struct  __block_impl {
  void *isa; // block 的 isa,所以说 block 本质是一个 oc 对象
  int Flags; // c++中构造方法需要的字段,没什么用
  int Reserved;
  void *FuncPtr; // block 内部的代码是封装到了一个函数中,通过调用这个函数来执行 block 内部的代码块,FuncPtr中保存的就是这个函数的地址
}
struct __main_block_desc_0 {
  size_t  reserved; //保留字段
  size_t Block_size; //block的大小
}

block 的变量捕获

  • 为了保证 block 内部能够正常访问外部的变量,block 有个变量捕获机制
  • 如果是全局变量,直接捕获指针,如果是局部变量则捕获变量值,如果是 static 局部变量则捕获指针


    image.png
-(void)test {
  int age = 10;
  static int height = 10;
  void (^block)(void) = ^{
    NSLog(@"age is %d, height is %d",age,height); //age is 10 height is 20, 
    /*这是因为 age 变量有可能在 block 调用的时候被释放掉,*/
    /*如果在 block 调用的时候 age 被释放掉了就会发生错误,*/
    /*所以不能捕获 age 变量的地址,而对于 height 变量,*/
    /*因为是 static 变量所以一直在内存中不会释放,所以捕获的是内存地址*/
  }
  height = 20;
  block();
}

block 类型

  • block 有 3 中类型,可以通过 class 方法或 isa 指针查看具体类型,最终都是继承自 NSBlock,NSBlock 继承自 NSObject
  • 全局 block _NSGlobalBlock(_NSConcreteGlobalBlock ),没有访问 auto 变量,存放在程序的数据区
  • 栈 block _NSStackBlock(_NSConcreteStackBlock ),访问了 auto 变量
  • 堆 block _NSMallocBlock(_NSConcreteMallocBlock ),_NSStackBlock调用了 copy
三种 block调用 copy 函数的结果
  • _NSGlobalBlock: 什么也不做,依旧存放在程序的数据区
  • _NSStackBlock:从栈复制到堆
  • _NSMallocBlock:引用计数+1
block的 copy
  • 在 ARC 环境下,编译器会根据情况自动将栈上的 block copy到,比如以下情况:
    1、block作为函数返回值时
    2、将block赋值给__strong指针时
    3、block作为Cocoa API中方法名含有usingBlock的方法参数时
    4、block作为GCD API的方法参数时
// ARC 环境下
typedef void(^Block)(void)
int age = 10;
Block block = ^{
  NSLog(@"age is %d",age);
}
NSLog(@"block is type %@",[block class]);  // block is type _NSMallocBlock
  • MRC 下 block 属性使用 copy
  • ARC 下 block 属性使用 strong 和 copy 都可以
对象类型的 auto 变量
  • 当 block 内部访问了对象类型的 auto 变量时,如果 block 是在栈上,将不会对 auto 变量产生强引用。如果 block 被 copy 到堆上,则会调用 block 内部的 copy函数,copy函数内部会调用_Block_object_assign函数,这个函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作形成强引用或者弱引用。如果block 从堆上移除时,会调用 block 内部的 dispose 函数。这个函数内部会调用_Block_object_dispose函数,这个函数会自动释放引用的 auto 变量

__block

  • __block可以用于解决block内部无法修改auto变量值的问题
  • __block不能修饰全局变量、静态变量(static)
  • 编译器会将__block变量包装成一个对象
@interface Person: NSObject
@end

int num = 20;
__block int age = 10;
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
__weak NSObject *weakObj = obj2;
Person *person = [[Person alloc] init];
__block __weak Person *weakPerson = person;  // 这里的 __weak 决定着__Block_byref_weakPerson_0结构体中指向 weakPerson 的指针是强引用还是弱引用
^{
  NSLog(@"%d",age);
  NSLog(@"%@",obj1);
  NSLog(@"%@",weakObj);
  NSLog(@"%@",weakPerson);
}
//以上代码转换成 c++后如下:
  struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0 *Desc;
  int num;
  __Block_byref_age_0 *age;
  NSObject *__strong  obj1;
  NSObject *__weak weakObject;
  __Block_byref_weakPerson_0 *weakPerson;
}
struct __Block_byref_age_0 {
  void *__isa;
   __Block_byref_age_0 *__forwarding; // 指向自己
  int __flags;
  int __size;
  int age;
}
struct __Block_byref_weakPerson_0 {
  void *__isa;
   __Block_byref_weakPerson_0 *__forwarding; 
  int __flags;
  int __size;
  void (* __Block_byref_id_object_copy)(void*,void*);
  void(* __Block_byref_id_object_dispose)(void*);
  Person *__weak weakPerson;
}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    // 如果 block 外部的变量没有用__block修饰的话,则不会有下面两个方法,下面两个方法对__block修饰的变量进行内存管理
   void (*copy)(struct __main_block_impl_0*,struct __main_block_impl_0*);
    void(*dispose)(struct __main_block_impl_0*);
}
image.png
__block的内存管理
  • 如果block 引用了外部变量,当 block从栈内存 copy 到堆内存时,会将外部引用的__block变量也 copy 到堆内存,并持有该变量;如果多个 block 引用同一__block变量,当block从栈 copy 到堆时,__block变量的引用计数会相应的增加。


    image.png
__block的__forwarding指针
  • 当 block 在栈上时__forwarding指针指向自己
  • 当 block 从栈复制到堆之后,栈上block 的__forwarding指针指向堆内存上的__block变量,而堆内存上的 block 的__forwarding 指针指向自己,这样在访问 age 时不论是访问栈上的变量,还是访问堆上的变量都可以通过age->__forwarding->age 访问到堆内存的 age


    image.png

block 问题

  • 在使用 block 时,当 block 内部引用了 self,我们通常在 block 外部声明一个 weakSelf,然后在 block 内再声明一个 strongSelf,这是为什么?
    1、当我们在 block内部使用 weakSelf 的时候,编译器会报错Dereferencing a __weak pointer is not allowed due to possible null value caused by rece condition, assign to strong variable first
    2、防止当我们执行 block 代码块的时候外部 self 已经被释放掉

相关文章

  • iOS开发之Block原理探究

    Block概述 Block本质 Block调用 Block分类 Block循环引用 Block原理探究 Block...

  • block的使用

    定义block 返回类型 (^block名称)(参数) = ^(){block内容}; 调用block block...

  • Block 02 - __block

    Block 02 - __block __block 的作用 __block 可以解决 Block 内部无法修改 ...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS Block

    Block的分类 Block有三种类型:全局Block,堆区Block,栈区Block 全局Block 当Bloc...

  • iOS block 为什么官方文档建议用 copy 修饰

    一、block 的三种类型block 三种类型:全局 block,堆 block、栈 block。全局 block...

  • iOS开发block是用copy修饰还是strong

    Block分为全局Block、堆Block和栈Block1、在定义block没有引用外部变量的时候,block为全...

  • block 初探

    全局block, 栈block, 堆block

  • Block

    一、Block本质 二、 BlocK截获变量 三、__block 修饰变量 四、Block内存管理 五、Block...

网友评论

      本文标题:block

      本文链接:https://www.haomeiwen.com/subject/qykagctx.html