Block变量捕获

上一篇《Block本质的探究》是在比较简单场景之下去探究的,当然不论怎么复杂的场景,本质的一些东西是不会变的。
这一篇主要来探究一下Block的变量捕获,下面开始进入正题!

一、 Block接收参数

有下面的一段代码, block接收两个参数,这种方式有什么不一样的呢?

 //block定义
void (^myBlock)(int, int) = ^(int num1, int num2){
      NSLog(@"Hello Block!-- a=%d - b = %d", num1, num2);
 };
 //block调用
myBlock(10, 20);

编译后

 //block定义
 void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0(
    __main_block_func_0, 
&__main_block_desc_0_DATA));

   //block调用
 ((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 20);

---------- 去除强制转换后 ------

//block定义
 void (*myBlock)(int, int) = &__main_block_impl_0(
      __main_block_func_0,
     &__main_block_desc_0_DATA)
);
//block调用
myBlock->FuncPtr(myBlock, 10, 20);

__main_block_func_0这个结构体封装函数增加了两个参数

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int num1, int num2) {
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_bee7ad_mi_0, num1, num2);
}

从上面不难发现,定义的时候传进去的参数没有什么变化,只有在调用的时候把参数分别放到了FuncPtr(__main_block_func_0)函数的第二和第三个参数。

二、 Block捕获外部变量

在探究Block捕获外部变量之前,我们先了解一下Block的变量捕获机制

1、 Block变量捕获机制

Block为了能够正常访问外部变量,有了一个变量捕获机制。

变量类型 捕获到block内部 访问方式
局部变量 auto 值传递
局部变量 static 指针传递
全部变量 static 直接访问

补充:

  • 局部变量默认就是auto修饰,写的时候我们可以省略,即 int num1 = 10; 其实就是auto int num1 = 10;`
  • auto: 自动变量,离开作用域就销毁
2、 Block捕获auto局部变量

我们来看一段代码:

int num1 = 10;
//定义一个block
void (^myBlock)(void) = ^{
     NSLog(@"Hello Block!-- num1 =%d", num1);
};
 num1 = 20; //修改一下num1的值
//block调用
myBlock();  //输出Hello Block!-- num1 =10

编译后

int num1 = 10;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
    (void *)__main_block_func_0, 
    &__main_block_desc_0_DATA, 
    num1
));

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

从上面可以看出num1的值直接传到__main_block_impl_0(..)这个构造函数里了。那既然传递到构造函数里,不难猜测,里面的__main_block_impl_0结构体肯定会有些变化了,下面来探究一下这些变化。

看下__main_block_impl_0结构体:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int num1;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num1, int flags=0) : num1(_num1) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 首先可以看到多了一个参数同名的成员 int num1,这个值就是用来存储外面传进来参数值。
  • 构造函数后面多了个 :num1(_num1),这个是c++语法,类似把参数_num1赋值给num1,即 num1 = _num1

再看下__main_block_func_0封装函数调用的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int num1 = __cself->num1; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_2a5abb_mi_0, num1);
}
  • 相比没有捕获外部变量,多了int num1 = __cself->num1; 也就是通过__cself拿到__main_block_impl_0里面的成员 num1的值,而原来__main_block_impl_0里面的num1存储的值就10。

而当执行num1 = 20这句代码的时候,改变的仅仅是修改了外面定义的num1变量的值。这也是为什么最后打印出来的值依旧是 10的原因。//输出Hello Block!-- num1 =10

3、 Block捕获static局部变量

我们来看下面的代码:

auto int num1 = 10;
static int age = 10;
//定义一个block
void (^myBlock)(void) = ^{
   NSLog(@"Hello Block!-- num1 =%d -- age = %d", num1,  age);
};
//block调用
num1 = 20;
age = 20;
//block调用
myBlock(); //输出Hello Block!-- num1 =10 -- age = 20

编译一下,看看有什么不一样

 //定义一个block
  void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
        (void *)__main_block_func_0, 
        &__main_block_desc_0_DATA, 
        num1,
       &age));
//block调用
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

我们看到定义的时候传给构造函数的参数不一样了:

  • auto 修饰的变量,传进去就num1的值
  • static 修饰的变量,传进去的age的地址值

再看结构体__main_block_impl_0里面的变化

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int num1;
  int *age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num1, int *_age, int flags=0) : num1(_num1), age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  • 捕获了两个外部变量
  • 与参数num1同名的成员num1来接收外面传进来的
  • 与参数age同名的 *age来接收外面传进来的地址值
  • : num1(_num1), age(_age) 分别进行赋值操作。

再看下__main_block_func_0封装函数执行逻辑的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int num1 = __cself->num1; // bound by copy
  int *age = __cself->age; // bound by copy
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_af9218_mi_0, 
            num1,
         (*age)
    );
}

block函数调用的时候, 通过__cself拿到对应的成员的值

  • int num1 = __cself->num1;, 取出的是值
  • int *age = __cself->age;,取出的是地址值
  • 注意看NSLog函数里面如何使用的这两个值:
    (*age)这个操作就是取出*age所指向内存地址变量的值

所以,到这里应该不难理解为什么程序运行之后输出的结果是Hello Block!-- num1 =10 -- age = 20

三、 Block访问全局变量

下面来看看 Block内部访问全局变量的情况,我们来看下面的代码:

int num1 = 10;
static int age = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       //定义一个block
        void (^myBlock)(void) = ^{
            NSLog(@"Hello Block!-- num1 =%d -- age = %d", num1,  age);
        };
        num1 = 20;
        age = 20;
       //block调用
        myBlock();  
    }
    return 0;
}

编译以后:

#pragma clang assume_nonnull end

int num1 = 10;
static int age = 10;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_f263c3_mi_0, 
      num1, 
      age);
}

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; 
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
            (void *)__main_block_func_0, 
            &__main_block_desc_0_DATA));
        num1 = 20;
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}

通过查看编译后的代码,我们可以发现:

  • __main_block_impl_0结构体里面并没有增加新的成员变量,即没有捕获外面的变量
  • __main_block_impl_0 (..)构造函数也没有接收其他额外的参数。
  • __main_block_func_0 (..)封装了函数执行逻辑的函数也没有接收额外的参数
  • NSLog (..)函数调用的时候,直接访问了num1 和 age

所以,为什么程序运行输出的结果是:Hello Block!-- num1 =20 -- age = 20也是不难理解的。

关于Block捕获外部变量的探究就先到这里啦!!!

你可能感兴趣的:(Block变量捕获)