iOS Block 实质 一

Block是“带有自动变量值的匿名函数”,但Block究竟是什么呢?
Block语法看上去很特别,但它实际上是作为极普通的C语言源码来处理的。通过支持Block的编译器,含有Block语法的源代码转换为一般的C语言编译器能够处理的源代码,并作为即为普通的C语言源代码被编译。
这不过是概念上的问题,在实际编译时无法转成能理解的源代码,但clang(LLVM编译器)具有转换为可读源码的功能。通过“-rewrite-objc”选项就能将含有Block语法的源码变换为C++代码。说是C++,其实也仅是使用了struct结构,起本质是C语言源码。

clang -rewrite-objc 源代码文件名

Block代码:

int main() {
    void (^block) (void) = ^ {
        printf("Block\n");
    };
    block();
    return 0;
}

此源码的Block语法最为简单,它省略了返回值类型以及参数列表。该代码通过clang可变换为一下形式:

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("Block\n");
}

static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

int main() {
    void (*block)(void) = (void (*) (void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    
    ((void (*) (struct __block_impl *))((struct __block_impl *)block)->FuncPtr)((struct __block_impl *)block);
    return 0;
}

8行的代码竟然增加到了43行。但是如果仔细观察就能发现,这段源码虽长却不那么复杂。下面,我们将源码分成几部分逐步理解。首先看最初的Block语法。

^{
    printf("Block\n");
};

可以看到,变换后的源码中也含有相同的表达式

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("Block\n");
}

如变换后的代码所示,通过Blocks使用的匿名函数实际上被作为简单的C语言函数来处理。另外,根据Block语法所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。
该函数的参数_cself相当于C++实例方法中指向实例自身的变量this,或是OC中指向对象自身的变量self,及参数__cself为指向Block值的变量。

遗憾的是,由这次Block语法变换而来的__main_block_func_0函数并不使用__cself。

struct __main_block_impl_0 *__cself;

与C++的this和Objective-C的self相同,参数__cself是__main_block_impl_0结构体的指针。
该结构体生命如下:

struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0 *Desc;
    }

由于转换后的源代码中,也一并写入了其构造函数,所以看起来稍显复杂,如果除去该结构函数,__main_block_impl_0结构体会变得非常简单。第一个成员变量时impl。

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

我们从其名称可以联想到某些标志、今后版本升级所需的区域以及函数指针。这些会在后面详细说明。第二个成员变量时Desc指针,一下为其结构体的声明:

struct __main_block_desc_0 {
        unsigned long reserved;
        unsigned long Block_size;
    };

这些也如同其成员名称所示,其结构为今后版本升级所需的区域和Block的大小。
初始化含有这些结构体的__main_block_impl_0结构体的构造函数

__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结构体成员的源代码。
_NSConcreteStackBlock用于初始化__block_impl结构体的isa成员。
void (block)(void) = (void () (void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
因为转换较多,看起来不是很清楚,所以我们去掉转换的部分,具体如下:

struct __main_block_impl_0() tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *block = &tmp;

这样就容易理解了。该源码将__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量block。以下为这部分代码对应的源代码:

void (^block) (void) = ^ {
    printf("Block\n");
};

将Block语法生成的Block赋给Block类型变量block。它等同于将__main_block_impl_0结构体实例的指针赋值给变量block。该源码中的Block是__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例。
下面就来看看__main_block_impl_0结构体实例构造参数:

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

第一参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。以下为__main_block_desc_0结构体实例的初始化部分代码。

static struct __main_block_desc_0 __main_block_desc_0_DATA {
        0,
        sizeof(struct __main_block_impl_0)
}

由此可知,该源代码使用Block,即__main_block_impl_0结构体实例的大小,进行初始化。
下面看看栈上__main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。如果展开__main_block_impl_0结构体的__block_impl结构体,可记述为如下形式:

struct __main_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0 *Desc;
}

该结构体根据构造函数会像下面这样进行初始化。

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

将__main_block_func_0函数指针赋值给成员变量FuncPtr。

block();

这部分可变换为一下源码:

((void (*) (struct __block_impl *)) ((struct __block_impl *)block)->FuncPtr)((struct __block_impl *)block);

去掉转换部分

(*block->impl.FuncPtr)(block);

这就是简单地使用函数指针调用函数。正如刚才确认的,由Block语法转换的__main_block_func_0函数指针被赋值成员变量FuncPtr中。另外也说明了,__main_block_func_0函数的参数_cself指向Block值。在调用该函数的源代码中可以看到Block正是作为参数进行了传递。
_NSCreteStackBlock到底是什么?

isa = &_NSConcreteStackBlock;

将Block赋值给Block的结构体成员变量isa。为了理解它,首先要理解Objective-C类和对象的实质。其实,所谓Block就是Objective-C对象。
“id”这一变量类型用于存储Objective-C对象。在Objective-C源代码中,虽然可以像使用void *类型那样随意使用id,但此id类型也能够在C语言中声明。在/usr/include/objc/runtime.h中是如下进行声明的:

typedef struct objc_object {
    Class isa;
} *id;

id为objc_object结构体指针类型。我们再来看看Class。

typedef struct objc_class *Class;

Class为objc_class结构体的指针类型。objc_class结构体在/usr/include/objc/runtime.h中声明如下:

struct objc_class {
    Class isa;
};

这与objc_object结构体相同。然而,objc_object结构体和objc_class结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。下面我们通过编写简单的Objective-C类声明来确认一下。

@interface MyObject: NSObject {
    int val0;
    int val1;
}
@end

基于objc_object结构体,该类的对象的结构体如下:

struct MyObject {
    Class isa;
    int val0;
    int val1;
};

MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。“Objective-C中由类生成对象”意味着,像该结构体这样“生成由该类生成的对象的结构体实例”。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。

类的结构体指针.png

各类的结构体就是基于objc_class结构体的class_t结构体。class_t结构体在objc4运行时库的runtime/objc-runtime-new.h中声明如下:

struct class_t {
    struct class_t *isa;
    struct class_t *superclass;
    Cache cache;
    IMP *vtable;
    uintptr_t data_NEVER_USE;
}

在Object中,比如NSObject的class_t结构体实例以及NSMutableArray的class_t结构体实例等,均生成并保持各个类的class_t结构体实例。该实例持有声明的成员变量、方法的名称、方法的实现(即函数指针)、属性以及父类的指针,并被Objective-C运行时库所使用。
在Block结构体此__main_block_impl_0结构体相当于基于objc_object结构体的Objective-C类对象的结构体。另外,对其中的成员变量isa进行初始化,具体如下:

isa = &_NSConcreteStackBlock;

即_NSConcreteStackBlock相当于class_t结构体实例。在将Block作为Objective-C的对象理解时,关于该类的信息放置于_NSConcreteStackBlock中。
所以说,Block的实质,即为Objective-C对象。

你可能感兴趣的:(iOS Block 实质 一)