美文网首页
Block笔记(三)

Block笔记(三)

作者: MichealXXX | 来源:发表于2018-10-24 19:10 被阅读0次

上一节我们讲到了Block的本质以及其实现原理,这一节我们就来看一下Block中的一些特性的实现原理是怎样的。

截获自动变量值

之前第一节我们讲到过Block可以截获自动变量的值,当我们定义一个自动变量,在执行Block语法之后,即使改变Block中使用的自动变量值不会影响Block执行时自动变量的值。下面我们就用源码举例说明。

int main(){
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt,val);};
    
    val = 2;
    fmt = "these values were changed. val = %d\n";
    
    blk();
    
    return 0;
}

我们通过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; // bound by copy
  int val = __cself->val; // bound by copy
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 dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

    val = 2;
    fmt = "these values were changed. val = %d\n";

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

    return 0;
}

与之前的转换稍有不同,我们来看看其中的差异,首先我们看到Block语法中使用的自动变量被作为成员变量追加到了__main_block_impl_0结构体中。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
};

请注意Block中没有使用自动变量不会被追加,比如源代码中的变量dmy,Block的自动变量截获只针对Block中使用自动变量。下面我们再来看看初始化结构体实例构造函数的差异。

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val)

在初始化结构体实例时,根据传递给构造体函数参数由自动变量追加的成员变量进行初始化。以下通过构造函数调用确认其参数。

 void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

使用执行Block语法时的自动变量fmt和val初始化__main_block_impl_0结构体实例,即在该源码中__main_block_impl_0结构体实例的初始化如下:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
fmt = "val = %d\n";
val = 10;

由此可知,在__main_block_impl_0结构体实例中,自动变量被截获。下面我们再来看一下使用Block的匿名函数的实现:

^{printf(fmt,val);};

该源代码可转换为以下函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
printf(fmt,val);}

在转换后的源代码中,截获到__main_block_impl_0结构体实例成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义。

总的来说在执行Block语法时,Block语法表达式所使用的自动变量值保存Block的结构体实例中,所以之后即使更改其值也不会造成影响。

__block说明符

通过上面的讲解我们明白了Block截获自动变量值的原理,那么如果我们在Block语法中更改自动变量的值会怎样呢?

int main(){
    int val = 10;

    void (^blk)(void) = ^{val = 1;};
    
    blk();
    
    return 0;
}

该源代码会产生以下编译错误:

Variable is not assignable (missing __block type specifier)

如前所述,因为在实现上不能改写被截获自动变量的值,所以编译器在编译过程中检出这种操作便会产生编译错误,这样一来就无法在Block中保存值了,极为不便。

在Block的第一篇我们提到过在函数的多次调用之间能够传递值的变量有三种,静态变量(静态局部变量)静态全局变量全局变量

我们现在就来看一下这三种变量在Block中是如何运作的:

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;
        
    };
    
    return 0;
}

这段源码使用Block改写静态变量static_val静态全局变量static_global_val全局变量global_val。该源代码转换后如下:

int global_val = 1;

static int static_global_val = 2;


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));

    return 0;
}

我们可以看到对静态全局变量全局变量的访问与转换前相同,因为这两个变量的作用域可以作用整个文件,Block可以直接访问,但是对于静态局部变量static_val就不太一样了。我们来看一下转换后的函数是如何使用该变量的:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

   (*static_val) *= 3;

 }

使用了静态变量指针对其进行访问。将静态变量static_val指针传给__main_block_impl_0结构体的构造函数并保存。这是超出作用域使用变量最简单的方法。

这时大家一定会有疑问,为什么不用对待静态局部变量的方式对待局部变量呢?

这是因为由Block语法生成的Block上可以存有超过其变量作用域被截获的自动变量。如果变量作用域结束了,原来的自动变量会被废弃,因此我们无法使用指针去访问之前的自动变量了,但是静态变量则不同它储存在程序专用的数据存储区,它的作用域虽然只在函数中超出作用域之后虽然我们无法直接访问其值,但是它的地址依旧存在,所以我们可以使用其指针去访问。

下面还有一种方式去解决Block中不能保存值的问题,那就是使用__block说明符,全名叫做“__block存储域类说明符”

__block说明符类似于static,auto和register说明符,他们用于指定将变量设置到指定的存储域中。例如auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中。

我们来实际使用一下__block说明符,指定想在Block中变更的自动变量

int main(){
   __block int val = 3;

   void (^blk)(void) = ^{
       val = 1;
   };
   
   return 0;
}

源代码转换后如下:

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

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __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_byref_val_0 *val = __cself->val; // bound by ref

        (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(){
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};

    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说明符,代码量便极具增加,那么这个__block说明符是如何转换的呢?

__block int val = 3;

转换后的代码如下:

__Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
 0,
 sizeof(__Block_byref_val_0),
 3
};

__block修饰的变量竟然变成了结构体实例,即栈上生成的__Block_byref_val_0结构体实例。这个变量初始化为3,这个值也出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量成员变量。该结构体声明如下:

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

这个结构体中最后的成员变量val就是相当于原来的自动变量,从名字也可以看出来这一点。那么最关键的赋值代码又是怎样呢?

 ^{
       val = 1;
   };

该代码转化如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

  (val->__forwarding->val) = 1;
 }

在上文的静态变量赋值的时候使用了静态变量指针直接访问地址去修改变量的值,而__block变量赋值要比这个复杂一些,Block的__main_block_impl_0结构体实例持有指向__block变量__Block_byref_val_0结构体实例指针也就是__main_block_impl_0成员变量中的__Block_byref_val_0 *val

而这个__Block_byref_val_0结构体实例成员变量__forwarding持有指向该实例自身指针。通过成员变量__forwarding访问成员变量val(相当于原自动变量)。

(val->__forwarding->val) = 1;

就如同这句代码一样,简单明了的表达了访问的方式。至于成员变量__forwarding存在的意义又是什么呢?我们留在下一节再进行说明。

相关文章

网友评论

      本文标题:Block笔记(三)

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