文章目录
  1. 1. Blocks的实现
    1. 1.1. 编译转化
    2. 1.2. 分析

Blocks的实现

大家好,我叫LastDays,这是我的Blog,我在这里分享我的一切。

iOS内存管理之Blocks的实现(一)中我们称Block为”带有自动变量的匿名函数”。同样我们介绍了5种使用方法,但是看起来其实并没有什么。其实Block语法也是被LLVM进行了转化,转化为一般的C语言来做处理。

block的数据结构是开源的,感兴趣的可以在这里下载并查看

编译转化

先来看这么一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>



int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here..
void (^blk)(void) = ^{
printf("lastdays\n");
};

blk();

}
return 0;
}

我们使用以下命令将它转换为c代码来看一下源代码。

1
clang -rewrite-objc main.m

因为转换后的代码太多了,取出关键地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
__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) {

printf("lastdays\n");
}

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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

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

}
return 0;
}

简单的分析下:从代码中我们可以看出,我们的block函数做出了相应的转化

1
2
3
^{
printf("lastdays\n");
};

转化为:

1
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            printf("lastdays\n");
        }

看到这里,就应该能大致的明白了,我们使用的Blocks语法实际上就是被作为一种简单的c函数来处理的。编译器会将我们block内部的代码单独取出,LLVM会生成一个单独的c函数。因此,创建block就是声明一个struct,并且进行一个初始化,执行block其实就是调用转化后的c函数。

block中包含了被引用的自由变量(由struct持有),也包含了控制成分的代码块(由函数指针持有),符合闭包(closure)的概念。

分析

1
2
3
4
5
6
7
8
9
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;
}
};

以上代码就是我们刚才分析到的,对struct进行初始化,这个的_ main block_ impl_0也就是我们的block的数据结构。这里面定义了implDesc

  • impl :就是我们的结构描述
  • Desc:就是我们block的数据描述

还是顺藤摸瓜,先来观察_ block impl结构体,

1
2
3
4
5
6
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

其实这就是一些标记,标记一些block结构的信息,
我们继续分析,在这里可以看到这段代码:

1
2
3
4
5
6
__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;
}

这里就是一个初始化,具体初始化的是什么?我们需要看一下main函数里面是如何进行初始化的了:

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

描述下初始化就是这样的:

1
2
3
4
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_impl_0;
Desc = &__main_block_desc_0_DATA;

可以看到_ main block_ impl_0其实就是我们对应的block函数:

1
2
3
^{
printf("lastdays\n");
};

Desc = & __main _block desc 0 _DATA; 这里其实就是使用Block,对_ main block_ impl_0结构体的大小进行初始化、

我们在main函数中进行了如下调用:

1
blk();

对应的转化其实就是这样:

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

将一些转化去掉以后,就是这个样子:

1
(*blk)->FuncPtr)(blk);

很容易就能理解了,也就是我们上面说的,编译器会将我们block内部的代码单独取出,LLVM会生成一个单独的c函数。因此,创建block就是声明一个struct,并且进行一个初始化,执行block其实就是调用转化后的c函数。

那么重点的内容:

在上面从来没有分析过_NSConcreteStackBlock究竟是什么?为了理解它需要对Objective-C类和对象的实现原理需要有一些理解才可以。我曾将分享过关于runtime的东西:Runtime 学习笔记(一),希望对阅读的朋友有帮助。

block中的isa指向的是该block的Class。在block runtime中,定义了6种类:

  • _NSConcreteStackBlock 栈上创建的block
  • _NSConcreteMallocBlock 堆上创建的block
  • _NSConcreteGlobalBlock 作为全局变量的block
  • _NSConcreteWeakBlockVariable
  • _NSConcreteAutoBlock
  • _NSConcreteFinalizingBlock
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct objc_class {
Class isa;

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;

Runtime 学习笔记(一)已经说明,其实我们的_ main block_ impl_0结构体就相当于基于objc _object结构体的Objective-C类对象的结构体。

那么也就是说_NSConcreteStackBlock就相当于objc_class,将Block作为Objective-C的对象处理时,关于该类的信息放置在_NSConcreteStackBlock中。

介绍了这么多,其实Block的实质就是Objective-C的对象。

文章目录
  1. 1. Blocks的实现
    1. 1.1. 编译转化
    2. 1.2. 分析