3.编译器中的block

3.1 block的数据结构定义

我们通过大师文章中的一张图来说明:

上图这个结构是在栈中的结构,我们来看看对应的结构体定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

从上面代码看出,Block_layout就是对block结构体的定义:

isa指针:指向表明该block类型的类。

flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。

reserved:保留变量,我的理解是表示block内部的变量数。

invoke:函数指针,指向具体的block实现的函数调用地址。

descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。

variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。

3.2 block的类型

block有几种不同的类型,每种类型都有对应的类,上述中isa指针就是指向这个类。这里列出常见的三种类型:

_NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:

1
2
3
4
5
#include int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

_NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:

1
2
3
4
5
6
#include int main()
{
    char a = 'A';
    ^{ printf("%c\n",a); } ();
    return 0;
}

_NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:

1
2
3
4
5
6
7
8
9
10
11
12
void exampleB_addBlockToArray(NSMutableArray *array) {
    char b = 'B';
    [array addObject:^{
            printf("%c\n", b);
    }];
}
void exampleB() {
    NSMutableArray *array = [NSMutableArray array];
    exampleB_addBlockToArray(array);
    void (^block)() = [array objectAtIndex:0];
    block();
}

总结一下:

_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。

_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。

_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。

3.3 编译器如何编译

我们通过一个简单的示例来说明:

1
2
3
4
5
6
7
8
9
10
11
#import typedef void(^BlockA)(void);
__attribute__((noinline))
void runBlockA(BlockA block) {
    block();
}
void doBlockA() {
    BlockA block = ^{
        // Empty block
    };
    runBlockA(block);
}

上面的代码定义了一个名为BlockA的block类型,该block在函数doBlockA中实现,并将其作为函数runBlockA的参数,最后在函数doBlockA中调用函数runBloackA。

注意:如果block的创建和调用都在一个函数里面,那么优化器(optimiser)可能会对代码做优化处理,从而导致我们看不到编译器中的一些操作,所以用__attribute__((noinline))给函数runBlockA添加noinline,这样优化器就不会在doBlockA函数中对runBlockA的调用做内联优化处理。

我们来看看编译器做的工作内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import __attribute__((noinline))
void runBlockA(struct Block_layout *block) {
    block->invoke();
}
void block_invoke(struct Block_layout *block) {
    // Empty block function
}
void doBlockA() {
    struct Block_descriptor descriptor;
    descriptor->reserved = 0;
    descriptor->size = 20;
    descriptor->copy = NULL;
    descriptor->dispose = NULL;
    struct Block_layout block;
    block->isa = _NSConcreteGlobalBlock;
    block->flags = 1342177280;
    block->reserved = 0;
    block->invoke = block_invoke;
    block->descriptor = descriptor;
    runBlockA(&block);
}

上面的代码结合block的数据结构定义,我们能很容易得理解编译器内部对block的工作内容。

3.4 copy()和dispose()

上文中提到,如果我们想要在以后继续使用某个block,就必须要对该block进行拷贝操作,即从栈空间复制到堆空间。所以拷贝操作就需要调用Block_copy()函数,block的descriptor中有一个copy()辅助函数,该函数在Block_copy()中执行,用于当block需要拷贝对象的时候,拷贝辅助函数会retain住已经拷贝的对象。

既然有有copy那么就应该有release,与Block_copy()对应的函数是Block_release(),它的作用不言而喻,就是释放我们不需要再使用的block,block的descriptor中有一个dispose()辅助函数,该函数在Block_release()中执行,负责做和copy()辅助函数相反的操作,例如释放掉所有在block中拷贝的变量等。

4.总结

最新文章

  1. lcok-free简易实现
  2. Python’s SQLAlchemy vs Other ORMs[转发 4]peewee
  3. nodevalue
  4. 加州大学伯克利分校Stat2.3x Inference 统计推断学习笔记: Section 4 Dependent Samples
  5. PHP实现微信公众账号开发
  6. shell动态解析sql的binlog
  7. MapReduce自定义类输出的内容为内存地址
  8. des加密解密源码 C# key值问题
  9. [Node.js]expressjs简单测试连接mysql
  10. redis安装及数据类型简介(string、list、set、sorted_set、hash)
  11. Codeforces Round #296 (Div. 1) E. Triangles 3000
  12. ASP.NET vNext or .NET vNext?
  13. jQuery动态绑定
  14. nodejs 后台服务启动
  15. Intellij IDEA设置运行时Java参数
  16. SSH概念及常用操作汇总
  17. Appium+Java(三)搭建环境之踩过的坑
  18. 【网站管理6】_一个网站SEO优化方案
  19. leetcode 总结 动态规划问题小结
  20. (转)poj算法做题顺序

热门文章

  1. SpannableStringBuilder 用法浅析以及仿陌陌表情
  2. vsftpd总结
  3. 洛谷 - P3966 - 单词 - AC自动机
  4. 51nod 1021【区间DP】
  5. IT行业技术及程序员相关网站荟萃
  6. layui 关闭当前窗口,刷新父级页面
  7. autoResizing autoLayout和sizeClass
  8. PAT甲级——1134 Vertex Cover (25 分)
  9. tracert查网络问题
  10. DISTINCT 去重---SQL