序文

特別の存在下でブロックとしてObjective-C言語は、既によく知られています。他の言語では、だけでなく、JavaScriptやスウィフト閉鎖、ラムダ匿名関数内のpythonと同様のブロックを、実現しています。主にブロック上のこの記事では、コンパイラのフロントエンド打ち鳴らすの使用の本質を探求します。会場についてと打ち鳴らすするLLVMはじめObjective-Cのソースファイルのコンパイルプロセスを

Objective-CのスイッチC ++

私たちは打ち鳴らすを利用することができます-rewrite-objcC ++ファイルにObjective-Cのソースファイルを配置します。例私は、ファイルをmain.m次のように、ソースコードは次のとおりです。

1 
2
3
4
5
6
7
8
9
10
11
12


INTのmain(){ int型、A = 1、B = 2INT(^ブロック)(INTINT)= ^(INT NUM1、INT NUM2){ 戻り NUM1 + NUM2。 }。





INT和=ブロック(A、B)。
printf("%のDN" 合計)。リターン0 ; }


C ++ソースコードに打ち鳴らすのmain.mを使用してください。そして、C ++ファイルを生成します。Objective-Cのソースコードの作者はの#import <財団/ Foundation.h> C ++変換されたファイルに結果30,000人以上の行を持っているので。しかし、最後の30行におけるキーコードは、調整後(ここで調整は、ヘッダファイル内のコードの一部、ファイルの末尾のコードのいくつかは、より多くの問題を読み取るにつながるために調整されるC ++ソースコードの位置の著者です、次のようにキーソースの最初のコードの末尾)にファイルを貼り付けます。

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
構造体 __ { ボイド *のISA。int型のフラグを。INT予約済み。無効 * FuncPtr。}。






静的 構造体 __のmain_block_desc_0 { size_tのは、予約済み。size_tの BLOCK_SIZE; } __main_block_desc_0_DATA = { 0はsizeof(構造体__main_block_impl_0)}。




構造体 __ main_block_impl_0 { 構造体 __のIMPL構造体 __ main_block_desc_0 * DESC ; __main_block_impl_0(ボイド * FP、構造体__main_block_desc_0 * DESC、 int型フラグ= 0){ impl.isa =&_NSConcreteStackBlock。 impl.Flags =フラグ。 impl.FuncPtr = FP; DESC = DESC。 }}。










静的 int型 __main_block_func_0(構造体__main_block_impl_0 * __ cself、int型 NUM1、int型からnum2){ 戻り NUM1 + NUM2。}



INT メイン () { int型、A = 1、B = 2int型(*ブロック)( int型 int型)=(( INT(*)( int型 int型))&__ main_block_impl_0((無効)* __ main_block_func_0、&__ main_block_desc_0_DATA)); int型の合計=(( INT(*)(__ block_impl *、 INT INT))((__ block_impl *)ブロック) - > FuncPtr)((__ block_impl *)ブロック、B)。printf "%のDN" 合計)。リターン0 ; }







静的 構造体 IMAGE_INFO { 符号なしのバージョン。符号なしフラグ。} _OBJC_IMAGE_INFO = { 02 }。

変換後のC ++のコードでは、3つの構造を含む:__ block_impl、__ main_block_desc_0、__ main_block_impl_0 と機能__main_block_func_0私たちは、C ++のブロックの性質の段階の分析により主な機能、ステップからカット。

C ++ソースコード解析

すでに述べたように、C ++のコード変換は3つの構造と機能を備えて、我々は以下の一つ一つを分析しました。

__block_impl

構造は、4つのメンバ変数を含む基本構造を説明するために使用されるブロックは、__block_implあります。また、__ block_implは共通の構造は、この用語は、通用他のブロックの基底構造を指す依然として__block_implあります。

1 
2
3
4
5
6
構造体 __ { ボイド *のISA。int型のフラグを。INT予約済み。無効 * FuncPtr。}。





  • isa。和Objective-C对象一样,Block也包含一个isa指针,且isa指针作为结构体的第一个成员变量,指向block的所属类型。默认初始化为_NSConcreteStackBlock的地址。即impl.isa = &_NSConcreteStackBlock;
  • Flags。Flags作为结构体的第二个成员变量,默认被置为0。对我们理解block的本质无实际意义,不展开讨论。
  • Reserved。Reserved作为结构体的第三个成员变量,是一个保留字段,暂未被使用。对我们理解block的本质无实际意义,不展开讨论。
  • FuncPtr。FuncPtr是一个函数指针,作为结构体的第四个也是最后一个成员变量。这个函数指针用于指向block的定义。Objective-C层面调用block底层就是调用的这个函数指针。

    __main_block_impl_0

    __main_block_impl_0是用来描述block实现的结构体,这个结构体是编译器根据上下文,动态生成并插入进来的。

    这个结构体的命名是有规律的:结构体名称前面的main是指包含block定义的那个函数或方法。结构体名称后面的数字0是指当前这个block是函数内的第几个block。从0开始,此处main函数中就只有1个block,所以动态生成的结构体名称即为__main_block_impl_0。所以这个结构体与通用结构体__block_impl不同,__main_block_impl_0并非一个通用结构体,Objective-C层面的每一个block在底层都有一个与之对应的用来描述其实现的结构体。当然,从另一个角度:这个结构体是编译器根据上下文,动态生成并插入进来的也可以断定这个结构体的非通用性。
    同样,__main_block_impl_0不仅包含一些成员变量,也包含一个构造方法,从这个角度看,__main_block_impl_0更像一个类。本质上,C++中的结构体和类没太大区别。
    C++结构体和类题外话:struct和class除了成员变量的访问权限不同,其他都是相同的。就连在内存中的表现都是一模一样的。struct的默认成员访问权限是public;class的默认成员访问权限是private。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct __main_block_impl_0 {
    struct __ 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;
    }
    };

__main_block_impl_0成员变量和成员函数如下:

  • impl。这是一个__block_impl类型的成员变量。__block_impl作为结构体__main_block_impl_0的第一个成员变量。这很有用,将在下文展开讨论。
  • Desc。这是一个__main_block_desc_0类型的成员变量。__main_block_desc_0将在下文介绍。
  • __main_block_impl_0。这是一个与结构体同名的成员函数,与其说这是一个成员函数,不如说这是一个构造方法。该构造方法和其他语言中的构造方法一样,可以初始化并返回一个实例对象。__main_block_impl_0函数接收两个外部参数(除了flags之外),然后对其成员变量impl和Desc进行配置并返回一个__main_block_impl_0类型的实例对象。通过__main_block_impl_0函数的实现不难看出,该构造函数主要配置了impl的isa指针(指向&_NSConcreteStackBlock,即栈block)impl的Flags使用默认参数设置为0。impl的函数指针FuncPtr指向了外部传递进来的参数fp。至于fp是在哪里传递进来的下文有介绍。

__main_block_desc_0

__main_block_desc_0同样是一个结构体,用来描述block的其他信息,本例中主要包括block的size。同样,__main_block_desc_0也是编译器根据上下文,动态生成并插入进来的。并且和结构体__main_block_impl_0存在同样的命名规律,即__block所在函数_block_impl_block在当前函数中的下标
__main_block_desc_0的定义如下:

1
2
3
4
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)};
  • reserved。这是一个保留字,目前没有实际意义。
  • Block_size。描述block所占的内存空间大小,对我们理解block的本质也无实际意义。

观察上面代码,紧随__main_block_desc_0的定义之后即声明了一个实例对象__main_block_desc_0_DATA。且该实例对象的reserved被设置为0,Block为size被设置为结构体__main_block_impl_0所占用的内存大小。

__main_block_func_0

__main_block_func_0是一个静态函数。观察其定义,可以看出__main_block_func_0的定义就是Objective-C层面block的定义,所以将来调用__main_block_func_0就相当于调用block的函数体。如下:

1
2
3
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int num1, int num2) {
大专栏  利用Clang探究block的本质ass="line"> return num1 + num2;
}

main 函数

阅读以上C++源码,可以看出main函数的定义在28—34行之间。如下:

1
2
3
4
5
6
7
int main () {
int a = 1, b = 2;
int (*block)(int, int) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
int sum = ((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);
printf("%dn", sum);
return 0;
}

第2行声明并定义了两个变量a和b。
第3行代码如下:

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

去掉类型转换对代码进行精简之后如下:

1
`int (*block)(int, int) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);`

实际上就是调用__main_block_impl_0函数实例化了一个名为block的__main_block_impl_0结构体对象,然后通过&符号取结构体对象地址。上面已经说过__main_block_impl_0函数是__main_block_impl_0结构体的构造方法。
__main_block_impl_0函数接收两个指针作为参数。第一个参数传递的是一个函数指针 —__main_block_func_0。学过C语言的应该知道,C语言中的函数名就是函数的地址。所以__main_block_impl_0就是指向这个函数的指针。通过查看__main_block_func_0的定义,可以得知__main_block_func_0的函数实现就是block的函数实现(即调用block要执行的代码块)。至此,我们知道,__main_block_impl_0函数的第一个参数是一个代表block的具体定义的函数。
__main_block_impl_0函数的第二个参数是一个__main_block_desc_0结构体实例,该结构体实例是__main_block_desc_0_DATA。上面已经说过,该结构体目前仅仅描述了block的size。
综上,第3行代码本质上就是实例化一个__main_block_impl_0结构体对象。

回过头再来看__main_block_impl_0结构体的定义:__main_block_impl_0结构体包含两个成员变量impl和Desc和一个与__main_block_impl_0结构体同名的构造函数__main_block_impl_0。其中成员变量impl是一个名称为__block_impl的结构体,impl是__main_block_impl_0的第一个成员变量。结构体__block_impl中主要包括两个指针isa和FuncPtr。__main_block_impl_0的第二个成员变量Desc(__main_block_desc_0类型)也是一个结构体,上面已经说过,__main_block_desc_0仅仅描述了block的size。结构体第三个成员变量是一个构造函数,该构造函数主要对impl这个结构体实例的isa和FuncPtr进行配置。其中isa设置为“&_NSConcreteStackBlock”,说明block的类型是栈block。FuncPtr被设置为外部参数fp。至此,可以得知,第3行调用构造函数初始化block时传递的函数指针__main_block_func_0被设置给了impl结构体的函数指针FuncPtr。

接下来继续分析第4行代码:

1
int sum = ((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);

通过查看第4行代码,可以看出该行代码是调用block。去除类型转换后,代码可以精简为

1
int sum = (((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);

以上代码,block是第三行__main_block_impl_0函数初始化而来,block本该是__main_block_impl_0类型的实例,这里却被强制转换为了__block_impl类型并且无论是在编译时还是运行时都不会报错也不会访问非法内存地址。归根到底,因为__block_impl是__main_block_impl_0结构体的第一个成员变量,block的内存起始地址就是它的__block_impl类型的成员变量impl的内存地址。换句话说,相当于将block_impl结构体的成员直接拿出来放在main_block_impl_0中,那么也就说明block_impl的内存地址就是main_block_impl_0结构体的内存地址开头。所以可以转化成功。并且可以合法的访问FunPtr。

block 被强转为__block_impl类型,就可以访问FuncPtr函数,block->FuncPtr接收了block、a、b三个参数。还记得FuncPtr这个函数指针的由来吗?FuncPtr就是在第3行中传入的函数指针__main_block_func_0。上面已经说过__main_block_func_0就是block的实现。所以执行block->FuncPtr就相当于执行__main_block_func_0。至此,block的调用结束。

通过以上分析,得知,block的定义本质上就是实例化一个__main_block_impl_0结构体对象。block的调用就是调用这个结构体对象内的成员变量impl的名为FuncPtr的函数指针。其中FuncPtr指针指向了block的实现(即block代码块)

增加注释后的C++源码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

struct __ {
void *isa; // isa是一个指针,指向block所属类型(栈block、堆block等)
int Flags;
int Reserved;
void *FuncPtr; // FuncPtr是一个函数指针,指向block代码块的定义,即调用block时执行的代码
};

// block的描述信息
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
// __main_block_desc_0类型的实例
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// block实现结构体
struct __main_block_impl_0 {
// block结构体实例
struct __block_impl impl;
// block描述
struct __main_block_desc_0* Desc;
// 实例化block实现结构体的构造方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
// 设置block类型为栈block
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
// 设置block的函数指针
impl.FuncPtr = fp;
// 设置描述信息
Desc = desc;
}
};

// block的函数体实现/定义
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int num1, int num2) {
return num1 + num2;
}

int main () {
int a = 1, b = 2;
// 定义结构体实例变量block:构造一个__main_block_impl_0类型的结构体实例。调用了__main_block_impl_0这个构造函数。
// 函数__main_block_impl_0接受两个参数(通过__main_block_impl_0结构体及其结构体构造方法的定义也可得知),一个参数是函数指针FuncPtr,此处传递的是__main_block_func_0这个函数,该函数即是block的实现/定义。另一个参数是这个block的描述信息,主要包括block所占空间大小
int (*block)(int, int) = ((int (*)(int, int)) &__main_block_impl_0 ((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 下面是对应的源码中调用block的代码,即int sum = block(a,b);
// 此处调用的是block->FuncPtr函数,上面已经说过,FuncPtr就是指向函数__main_block_func_0的函数指针调用FuncPtr就相当于调用__main_block_func_0。此处传递的是block、a、b
int sum = ((int (*)(__block_impl *, int, int)) ((__block_impl *)block)->FuncPtr) ((__block_impl *)block, a, b);
printf("%dn", sum);
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

总结

知ることは困難この分析により、ブロックの基本的定義は、ISA内部ポインタを有する__main_block_impl_0構造、オブジェクトをインスタンス化され、これが構造をカプセル化し、関数は、関数呼び出しパラメータを呼び出します。FuncPtrは、関数ポインタコンストラクタがIMPLのボディ構造体変数内の構造にオブジェクトをインスタンス化するために呼び出されるように定義された転送をブロックします。コールブロックは、オブジェクトの構造内FuncPtrのIMPL呼び出された関数ポインタメンバ変数を呼び出すことです。前記ブロック(すなわち、ブロック・コード・ブロック)の実装にFuncPtrポインタ。これらの結論に基づいて、我々はまた、Liteの実装を行うのObjective-Cのを阻止するためにC ++やC言語を使用することができます。一般的な考え方:クラス定義やブロックの構造。ブロック構造またはオブジェクトBLKのクラスのインスタンスを使用する場合、対物-Cが定義され、そしてブロックは一時的関数ポインタとしての目的を達成するためにコードブロックに渡され、呼直接BLK Objective-Cのオブジェクトが呼び出されたときに、一時的なブロック関数ポインタ。