C高档编制程序,Block变量截获

  • 什么是Block?
  • Block变量截获
  • Block的几种形式

简书博客已经暂停更新,想看更多技术博客请到:

图片 1《Objective-C高级编程:iOS与OS X多线程和内存管理》

Block与对象

首先我们先反思几个问题:
block到底是不是对象?
如果是对象,和某个已定义的类的实例对象在使用上是不是一样的?
如果不一样,主要的区别是什么?

深入理解Objective-C的Block这篇文章做了很好的讲解。

  • 掘金 :J_Knight_
  • 个人博客: J_Knight_
  • 个人公众号:程序员维他命
Blocks是C语言的扩充功能——“带有自动变量的匿名函数”。
使用Blocks可以不声明C 和Objective-C类,也没有使用静态变量、静态全局变量或全局变量时的问题,仅用编程C语言函数的源代码量即可使用带有自动变量值的匿名函数。

弄清Block的存储域

直接上代码:

typedef void (^blk)(void);

blk global_blk = ^{};

int main() {

    int num = 5;
    blk local_global_blk = ^{NSLog(@"no use auto variable");};
    blk local_malloc_blk = ^{NSLog(@"use auto variable:%i", num);};
    NSArray *blks = [[NSArray alloc] initWithObjects:
                     global_blk,
                     local_global_blk,
                     local_malloc_blk,
                     ^{ NSLog(@"this is block 1:%i", num); },
                     [^{ NSLog(@"this is block 2:%i", num); } copy],
                     nil];
    NSLog(@"%@", blks);

    return 0;
}

打印结果:

**2016-12-23 10:22:31.917 IOSAPILearn[11561:33792561] (**
**    "<__NSGlobalBlock__: 0x1044d4bb0>",**
**    "<__NSGlobalBlock__: 0x1044d4bf0>",**
**    "<__NSMallocBlock__: 0x60000005bc30>",**
**    "<__NSStackBlock__: 0x7fff5b757598>",**
**    "<__NSMallocBlock__: 0x60000005be40>"**
**)**

由此看出Block的存储域有三种:

  • 数据区域.data区:_NSConcreteClobalBlock
  • 堆:_NSConcreteMallocBlock
  • 栈:_NSConcreteStackBlock

注意:Block对象与一般的类实例对象有所不同,一个主要的区别就是分配的位置不同,block默认在栈上分配,一般类的实例对象在堆上分配。

什么时候分配在数据域.data区?

  • 记述全局变量的地方有Block语法时。
  • Block语法的表达式中不截获自动变量时。

什么时候分配在栈?
除此之外Block语法生成的Block为_NSConcreteStackBlock类对象实例,且设置在栈上。

什么时候将栈上的Block复制到堆上呢?

  • 调用Block的copy实例方法。
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时。
  • Block作为函数返回值返回时,编译器会自动生成复制到堆中的代码。
  • Cocoa框架的方法且方法名中含有usingBlock等时。
  • Grand Central Dispatch的API

注意:retain是对一个在中分配内存的对象的引用计数做了增加,执行release操作的时候检查计数是否为1,如果是则释放中内存。对于Global的Block,我们无需多处理,不需retain和copy,因为即使你这样做了,似乎也不会有什么两样。对于Stack的Block,如果不做任何操作,随栈帧自生自灭。而如果想让它活得比stack 帧更久,那就调用Block_copy(),让它搬家到堆内存上。而对于已经在堆上的block,也不要指望通过copy进行“真正的copy”,因为其引用到的变量仍然会是同一份,在这个意义上看,这里的copy和retain的作用已经非常类似。

按配置Blcok的存储域,将copy方法进行复制进行的动作总结如下:

Block的类 副本源的配置存储域 复制结果
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteGlobalBlock 程序的数据区域 什么也不做
_NSConcreteMallocBlock 引用计数增加
  • ###### Block是将函数及其执行上下文封装起来的对象。

图片 2《Objective-C高级编程:iOS与OS X多线程和内存管理》

弄清Block如何实现截获自动变量值

Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值,因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写了Block中使用的自动变量的值也不会影响Block执行时自动变量的值。
看代码:

static int staic_global_variable = 10;
int global_variable = 12;
- (void)captureAutomaticVariable {
    int local_variable = 5;
    static int local_static_variable = 13;
    blk local_malloc_blk = ^{
        NSLog(@"use local_variable: %i", local_variable);
        NSLog(@"use local_static_variable: %i", local_static_variable);
        NSLog(@"use staic_global_variable: %i", staic_global_variable);
        NSLog(@"use global_variable: %i", global_variable);
    };
    local_variable = 0;
    local_static_variable = 0;
    staic_global_variable = 0;
    global_variable = 0;
    local_malloc_blk();
}

打印结果:

**2016-12-23 11:15:34.279 IOSAPILearn[13578:33912186] use local_variable: 5**
**2016-12-23 11:15:34.280 IOSAPILearn[13578:33912186] use local_static_variable: 0**
**2016-12-23 11:15:34.280 IOSAPILearn[13578:33912186] use staic_global_variable: 0**
**2016-12-23 11:15:34.280 IOSAPILearn[13578:33912186] use global_variable: 0**

可以看出以下三种变量超出与变量作用域的存在:

  • 静态变量 (传入地址实现)
  • 全局静态变量
  • 全局变量

使用$ clang -rewrite-objc -fobjc-arc main.m 看运行时实现方式:

注意:要进入main.m的目录下执行,才会看到main.cpp文件

关键代码:

typedef void (*blk)(void);
static int staic_global_variable = 10;
int global_variable = 12;

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;
  int local_variable;
  int *local_static_variable;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _local_variable, int *_local_static_variable, int flags=0) : local_variable(_local_variable), local_static_variable(_local_static_variable) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int local_variable = __cself->local_variable; // bound by copy
  int *local_static_variable = __cself->local_static_variable; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_0, local_variable);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_1, (*local_static_variable));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_2, staic_global_variable);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_3, global_variable);
    }

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 local_variable = 5;
    static int local_static_variable = 13;
    blk local_malloc_blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, local_variable, &local_static_variable));
    local_variable = 0;
    local_static_variable = 0;
    staic_global_variable = 0;
    global_variable = 0;
    ((void (*)(__block_impl *))((__block_impl *)local_malloc_blk)->FuncPtr)((__block_impl *)local_malloc_blk);

    return 0;
}

分析:
转换前的代码:

int local_variable = 5;
static int local_static_variable = 13;
blk local_malloc_blk = ^{
        NSLog(@"use local_variable: %i", local_variable);
        NSLog(@"use local_static_variable: %i", local_static_variable);
        NSLog(@"use staic_global_variable: %i", staic_global_variable);
        NSLog(@"use global_variable: %i", global_variable);
    };

转换后的代码:

int local_variable = 5;
static int local_static_variable = 13;
blk local_malloc_blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, local_variable, &local_static_variable));

为看的更清楚些,把强制转换去掉:

blk local_malloc_blk = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, local_variable, &local_static_variable)

可以看出local_malloc_blk是一个__main_block_impl_0结构体,__main_block_impl_0结构体使用__main_block_func_0,__main_block_desc_0_DATA,local_variable,&local_static_variable初始化,注意local_variable和&local_static_variable,一个传入值,一个传入地址。
下面来看看__main_block_impl_0的构成:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int local_variable;
  int *local_static_variable;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _local_variable, int *_local_static_variable, int flags=0) : local_variable(_local_variable), local_static_variable(_local_static_variable) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

可以看出__main_block_impl_0是一个类(有isa),它的成员变量impl中存储了一个FuncPtr(函数指针),从上面的初始化代码中可以看出__main_block_impl_0使用__main_block_func_0函数指针来初始化的。
__main_block_func_0的代码如下:

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

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_0, local_variable);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_1, (*local_static_variable));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_2, staic_global_variable);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_3, global_variable);
    }

是由local_malloc_blk闭包体转换过来的:

blk local_malloc_blk = ^{
        NSLog(@"use local_variable: %i", local_variable);
        NSLog(@"use local_static_variable: %i", local_static_variable);
        NSLog(@"use staic_global_variable: %i", staic_global_variable);
        NSLog(@"use global_variable: %i", global_variable);
    };

由上可知,闭包为一个__main_block_impl_0对象,闭包体转换为了一个__main_block_func_0函数,其中__main_block_func_0这个函数需要传入struct __main_block_impl_0 *__cself为参数。这就有点像我们平时函数体中的self了,__main_block_func_0使用__cself可以获取__main_block_impl_0中捕获到的自动变量local_variable和local_static_variable,从而执行对应的代码。
总结:

static int staic_global_variable = 10;
int global_variable = 12;

转换为运行时代码后并没有什么改变,__main_block_func_0中仍然按全局静态变量和全局变量使用,所以它们超出与变量作用域而存在。

int local_variable = 5; 
static int local_static_variable = 13;

被__main_block_impl_0捕获为了自己的变量,但是local_variable是传入值进去,而&local_static_variable是传入地址进去,在__main_block_func_0中使用时,用*local_static_variable来取值,所以,local_static_variable可以超出变量作用域而存在。

比如:

这一章讲解了Block相关的知识。因为作者将Objective-C的代码转成了C 的代码,所以第一次看的时候非常吃力,我自己也不记得看了多少遍了。

图片 3图片来自:《Objective-C高级编程:iOS与OS X多线程和内存管理》

使用__block修饰:

__block修饰符,更准确的表达方式为“__block存储域说明符(__block storage-class-specifier)”。用于指定Block中想变更值的自动变量。
代码如下:

- (void)useBlockSpecifier {
    __block int num = 5;
    blk captureBlk = ^{
        num  = 1;
        NSLog(@"num is = %i", num);
    };
    num = 1;
    captureBlk();
    captureBlk();
    NSLog(@"current num is = %i", num);
}

打印结果:

**2016-12-23 14:06:20.957 IOSAPILearn[19064:34269365] block num is = 2**
**2016-12-23 14:06:20.959 IOSAPILearn[19064:34269365] block num is = 3**
**2016-12-23 14:06:20.959 IOSAPILearn[19064:34269365] current num is = 3**

可以看出通过__block修饰的变量在闭包体中可以修改,闭包体内与闭包体外的num是同一个值,那它是如何做到的呢?

使用$ clang -rewrite-objc -fobjc-arc main.m 看运行时实现方式:

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

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__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_num_0 *num = __cself->num; // bound by ref

        (num->__forwarding->num)  = 1;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_ef88c5_mi_0, (num->__forwarding->num));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->num, 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_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 5};
    blk captureBlk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
    (num.__forwarding->num) = 1;
    ((void (*)(__block_impl *))((__block_impl *)captureBlk)->FuncPtr)((__block_impl *)captureBlk);
    ((void (*)(__block_impl *))((__block_impl *)captureBlk)->FuncPtr)((__block_impl *)captureBlk);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_ef88c5_mi_1, (num.__forwarding->num));

    return 0;
}

这里注意到__main_block_impl_0中对捕获的num是通过对象__Block_byref_num_0 *num //by ref来存储的。
__Block_byref_num_0定义如下:

struct __Block_byref_num_0 { 
    void *__isa;
    __Block_byref_num_0 *__forwarding;
    int __flags; 
    int __size; 
    int num; //用来保存num值
};

用__block修饰的num变量转化成了以下形式:

__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 5};

即创建一个__Block_byref_num_0对象num,使__forwarding指针指向栈/堆中的内存。

  • 当block在栈中时,__forwarding指向栈的。
  • 当block在堆中时,__forwarding指向堆的。

__forwarding用处:

  • 无论是在栈中还是在堆中的num使用__forwarding指向的都是同一个地址。
  • 所以可以在block体中修改变量值,也可以在block上下文中修改变量值,他们修改的是同一个值。

__main_block_copy_0和__main_block_dispose_0函数:
__main_block_copy_0是Block从栈上复制在堆上的时候调用
__main_block_dispose_0是Block从堆上销毁的时候调用

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
    _Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);
}

__Block_object_assign函数调用相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中。
__Block_object_dispose函数,释放对象类型的结构体的成员变量。

Objective-C的运行时库能够准确的把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block结构体中即使含有附有__strong修饰符或者__weak修饰符的变量也可以恰当的进行初始化和废弃。为此需要在结构体__main_block_desc_0中增加成员变量copy和dispose,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。

NSInteger num = 3; NSInteger(NSInteger) = ^NSInteger(NSInteger n){ return n*num; }; block;

这篇总结不仅仅只有这本书中的内容,还有一点在其他博客里看过的Block的相关知识,并加上了自己的理解,而且文章结构也和原书不太一致,是经过我的整理重新排列出来的。

本文由星彩网app下载发布于计算机编程,转载请注明出处:C高档编制程序,Block变量截获

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。