又见block(四):block捕获自动变量

block捕获自动变量主要分为:默认情况下和添加__block后进行捕获,下面分别进行讨论
1、默认情况下捕获自动变量
#import <stdio.h>
int main() {
    //  捕获默认情况下的自动变量
    int tes = 34;
    int val = 23;
    const char *fmt = "val = %d";
    void (^blk) (void) = ^{
        printf(fmt,val);
    };
    blk();
    return 0;
}

block捕获自动变量时,clang转换后的主要源代码为:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; 
  int val = __cself->val; 
        printf(fmt,val);
    }

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 tes = 34;
    int val = 23;
    const char *fmt = "val = %d";
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

可以看出,block中捕获的自动变量被追加到block结构体__main_block_impl_0后面,其声明的成员变量类型与自动变量类型完全相同,block中没有捕获的自动变量不会被追加。
使用执行block语言时的自动变量fmtval来初始化__main_block_impl_0结构体实例。

总的来说,默认情况下的捕获自动变量意味着在执行block语法时,捕获的自动变量值被保存到block结构体实例(block本身)中,相当于值传递

2、捕获有 __block 修饰的自动变量

默认情况下,修改block捕获的自动变量值,编译器会报错

那么如何在block中修改外部自动变量的值,主要有以下2种方法:
a、修改C语言中的静态变量静态全局变量全局变量
b、使用__block修饰需要修改的自动变量

1、修改C语言中的静态变量静态全局变量全局变量
block语法的匿名函数部分简单地变换成了C语言函数,但是从这个变换的函数中访问静态全局变量全局变量并没有任何不同,可直接使用
但是对于静态变量,转换后的函数原本就不在含有block语法的函数里面,超出了静态变量的作用域,所以不能访问静态变量。

#import <stdio.h>

//  全局变量
int global_val = 1;
//  静态全局变量
static int static_global_val = 2;
int main() {
    static int static_val = 3;
    void (^blk) (void) = ^{
        global_val *=1;
        static_global_val *=2;
        static_val *= 3;
    };
    blk();
    return 0;
}

clang转换后的源代码为:

int global_val = 1;
static int static_global_val = 2;
//  block 的结构体实例
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy
        global_val *=1;
        static_global_val *=2;
        (*static_val) *= 3;
    }

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() {
    static int static_val = 3;
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

可以看出,对于静态全局变量全局变量与转换前访问完全相同,但是静态变量是如何转换的呢?

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *static_val = __cself->static_val; 
      (*static_val) *= 3;
}

很显然,通过静态变量static_val的指针对其进行访问,将静态变量的指针传递给block结构体__main_block_impl_0的构造函数中并保存,这是超出作用域变量的最简单的方法

那么默认情况下的自动变量的访问为什么不采用静态变量的访问方式呢?
实际上,block语法生成的值block上,可以存有超过其变量作用域的被捕获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃,因此block中超过变量作用域而存在的变量同静态变量一样,不能通过指针访问原来的自动变量。

2、使用__block修饰需要修改的自动变量
更准确的表述方式为“__block存储域类说明符”,正如C语言中有以下几种存储域类说明符:

  • typedef:为某一类型自定义名称
  • extern:声明外部全局变量,只能声明,不能实现
  • static:作为静态变量存储在数据区中
  • auto:作为自动变量存储在栈中
  • register:请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。这里是尽可能,不是绝对
    __block说明符类似于static、autoregister说明符,它们用于指定将变量值设置到哪个存储域中
    __block int val = 10;
    void (^blk) (void) = ^{
        val = 1;
    };

clang转换后的源代码为

//  __block类型变量转换成结构体的声明
struct __Block_byref_val_0 {
  void *__isa;
  __Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

//  block结构体实例结构,追加了__Block_byref_val_0结构体成员变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // block中如何修改 __block 类型的变量
  __Block_byref_val_0 *val = __cself->val; 
        (val->__forwarding->val) = 1;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main() {
        // __block 类型变量的初始化
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
    (void*)0,
    (__Block_byref_val_0 *)&val,
     0, 
     sizeof(__Block_byref_val_0), 
     10
    };
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    return 0;
}

从上面源代码可以看出,

__block int val = 10;

__block变量同block一样转换成__Block_byref_val_0类型的结构体实例,存储在栈上,变量初始化值10出现在该结构体实例的初始化中,意味着该结构体持有相当于原自动变量的成员变量

 __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
    (void*)0,
    (__Block_byref_val_0 *)&val,
     0, 
     sizeof(__Block_byref_val_0), 
     10
    };

__Block_byref_val_0类型的结构体声明如下:

    struct __Block_byref_val_0 {
      void *__isa;
      __Block_byref_val_0 *__forwarding;
     int __flags;
     int __size;
     int val;
};

如同初始化的源代码,该结构体中最后的成员变量val相当于原自动变量的成员变量,那么在block中修改自动变量的代码又是被如何转换的,转换后的源代码如下:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // block中如何修改 __block 类型的变量
      __Block_byref_val_0 *val = __cself->val; 
        (val->__forwarding->val) = 1;
    }

之前在block中向静态变量赋值时,使用了指向该静态变量的指针,但是在__block变量中赋值的过程更为复杂,主要过程如下:

  • block的结构体实例__main_block_impl_0持有指向__block变量的__Block_byref_val_0结构体实例的指针
  • __Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针
  • 通过成员变量__forwarding访问成员变量val,成员变量val是该实例自身持有的变量,相当于原自动变量

多个block使用__block变量,情况如下:

    #import <stdio.h>
    int main() {
    __block int val = 10;
    void (^blk0) (void) = ^{
        val = 0;
    };
    void (^blk1) (void) = ^{
        val = 1;
    };
    return 0;
    }

clang转换后的源代码为

    void (*blk0) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    void (*blk1) (void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));

可见多个block同时使用了__Block_byref_val_0结构体实例val的指针,这样就可以实现多个block中使用同一个__block变量,反过来也是可以的,即一个block中使用多个__block变量,只要增加block的结构体__main_block_impl_0成员变量与构造函数的参数
又见block(一):block是什么?
又见block(二):block语法定义
又见block(三):block实质
又见block(五): __block变量和对象
又见block(六):block存储域与__block变量存储域
又见block(七):截获对象

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值