iOS--block的本质和变量捕获机制

学习笔记,如有错误,欢迎批评指正!!! 仅供学习交流...

block的本质

  1. block是带有自动变量(局部变量)的匿名函数(不带名称的函数)
    1. 带有自动变量的值:block保持自动变量的值。
  2. block的本质是一个OC对象,它内部有一个isa指针。

直接上代码,查看block的底层结构

void (^block1)(void) = ^{
            NSLog(@"This is block!!!");
        };

接下来,使用终端命令:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o xxx.cpp

然后,查看xxx.cpp文件,如下图所示(请忽略文件命名 ...)

1.jpg

接下来,我们把重点代码复制下来

//block转化成c++的结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //构造函数(类似于OC的init方法)
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

////封装了 block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zt_xy2f_gsx0mlbrzmvts93gjqm0000gn_T_main_b2615a_mi_0);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        //定义block变量
        void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        //执行block内部的代码
        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    }
    return 0;
}

对 main 函数里边的代码 进行简化:

//定义block变量
void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

//执行block内部的代码
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);

继续简化:

//定义block变量
//调用 __main_block_impl_0 构造函数,返回结构体对象,最后取地址,复制给 block1
//返回的结构体对象就是 struct __main_block_impl_0
void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

//执行block内部的代码
block1->FuncPtr(block1);

block的底层机构大致就是这样的:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //构造函数(类似于OC的init方法)
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

接下来,我们看一下这段代码:

void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

可以看到,__main_block_impl_0 函数传递了两个参数 (void *)__main_block_func_0&__main_block_desc_0_DATA

第一个参数 (void *)__main_block_func_0,这个函数里边封装的就是 block的执行逻辑代码

最终 这个函数指针赋值给了 struct __block_impl里边的void *FuncPtr。也就是说 FuncPtr里边存储的就是将来要执行的 block函数的地址。

2.jpg

接着,我们看 block的执行代码

((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);

//简化之后
block1->FuncPtr(block1);

可以看到,执行block内部的代码,其实是找到 FuncPtr指针,然后去执行。

block1之所以可以直接找到 FuncPtr,是因为 将 block1强制转换成了 __block_impl 这种类型,然后再找到 __block_impl里边的 FuncPtr

__main_block_impl_0 这种类型的block 之所以 能够转换成为 __block_impl类型,是因为 __main_block_impl_0里边的第一个成员就是 __block_impl类型的,这两个的地址是一样的。

从另一种方面来说,由于__main_block_impl_0类型的第一个成员的类型是 struct __block_impl,所以 __main_block_impl_0也可以直接看成是如下结构:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  //struct __block_impl impl;
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  struct __main_block_desc_0* Desc;
  //构造函数(类似于OC的init方法)
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

这样,就可以简单明了的理解 block为什么可以直接找到 FuncPtr了。

其实,我们还可以通过以下代码查看 block

void (^block1)(void) = ^{
            NSLog(@"This is block!!!");
        };
        
block1();
              
NSLog(@"%@ %@ %@ %@",[block1 class]
                      ,[[block1 class] superclass]
                      ,[[[block1 class] superclass] superclass]
                      ,[[[[block1 class] superclass] superclass] superclass]);

输出结果:
3.jpg

可以看到,block最终继承自 NSObject,再次证明 block其实就是一个OC对象

变量的捕获

为了保证 block 内部能够正常访问 外部变量的值, block有个变量捕获机制

C语言的函数中可能使用的变量

  • 自动变量(局部变量)-- 离开作用域就会自动销毁
  • 函数的参数
  • 静态局部变量
  • 静态全局变量
  • 全局变量

其中,静态局部变量静态全局变量全局变量,虽然这些变量的作用域不同,但是在整个程序当中,一个变量总保持在一个内存区域。因此,即使在多个函数中使用这些变量,这些变量的值总是保持不变。

那么,哪些变量可以被捕获到block内部呢?先看下边的结论

局部变量会被捕获到block内部,全局变量不会被捕获到block内部。

局部变量的捕获

直接上代码:
4.jpg

定义了 两个变量 a 和 b, 运行,查看输出结果

02-block变量捕获[2020:161392] a = 10, b = 20

接着看转换的c++代码。

5.jpg

可以看出,变量a 和 变量b 都被捕获到了 block 内部。并且,变量a 和 变量b 的捕获方式是一模一样的,都是直接把值传递进去(值传递)。

//这两句代码是等效的  默认前边都是有一个 auto 关键字
int a = 10; 
auto int a = 10; 

接下来,修改代码,如下图:

6.jpg

从运行输出结果可以看出 static修饰的局部变量 和 auto修饰的局部变量的结果是不一样的。

重新生成一下xxx.cpp代码,查看一下:

7.jpg

可以看出static修饰的局部变量 传递的是 地址值,被block捕获到内部的是一个指针。(指针传递)

关于局部变量的捕获:

  1. auto类型:会被捕获, 捕获方式:值传递
  2. static类型:会被捕获, 捕获方式:指针传递

全局变量的捕获

继续修改代码,如下如所示:

8.jpg

全局变量a 和 静态全局变量b 获取到的都是最新的值。

重新生成cpp文件

9.jpg

可以看出,全局变量a 和 静态全局变量b 并没有被捕获到 block内部。

总结:

10.jpg

结论
11.jpg

你可能感兴趣的:(iOS--block的本质和变量捕获机制)