块 -- 面向 Objective-C iOS 和 OS X 的高级编程 多线程和内存管理 -- 块的实现
Block的实质
Block语法实际上是作为极普通的C语言源代码来处理的。
通过支持Block的编译器,含有Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言源代码被编译。
Block其实就是Objective-C对象,因为它的结构体中含有isa
指针。
下面在终端通过clang将OC中Block语法转换为C++代码:clang -rewrite-objc main.m
main.m:
int main(void) {
void (^blockName) (void) = ^{
printf("Block\n");
};
blockName();
return 0;
}
main.cpp:
下面,我们将源代码分成几个部分逐步理解:
-
源代码中的Block语法
//void (^blockName) (void) = ^{printf("Block\n");}; //通过Blocks使用的匿名函数被作为简单的C语言函数来处理 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Block\n"); }
根据Block所属的函数名(此处为
main
)和该Block语法在该函数出现的顺序值(此处为0
)来给经clang变换的函数命名。
该函数的参数_cself
相当于C++实例方法中指向实例自身的变量this,或是OC实例方法中指向对象自身的变量self,即参数__cself为指向。C++的this,Objective-C的self
定义类的实例方法://C++ void MyClass::method(int arg) {printf("%p %d", this, arg);} MyClass cls; cls.method(10); //OC - (void)method: (int)arg {printf("%p %d", self, arg);} MyObject* obj = [[MyObject alloc] init]; [obj method: 10];
C++、Objective-C编译器将该方法作为C语言函数来处理:
//C++转成C void __ZN7MyClass6methodEi(MyClass* this, int arg) { printf("%p %d", this, arg); } struct MyClass cls; __ZN7MyClass6methodEi(&cls, 10); //OC转成C void _I_MyObject_method_(struct MyObject* self, SEL _cmd, int arg) { printf("%p %d", self, arg); } MyObject* obj = objc_msgSend(objc_getClass("MyObject"), sel_registerName("alloc")); obj = objc_msgSend(obj, sel_registerName("init")); objc_Send(obj, sel_registerName("method:"), 10);
objc_msgSend函数根据指定的对象和函数名,从对象持有类的结构体中检索_I_MyObject_method_函数的指针并调用。
objc_msgSend函数的第一个参数objc作为_I_MyObject_method_的第一个参数self进行传递。 -
来看看参数的声明:
struct __main_block_impl_0* __cself
,该结构体的声明如下:struct __main_block_impl { void* isa; int Flags; //标志 int Reserved; //今后版本升级所需的区域 void* FuncPtr; //指针函数 } struct __main_block_impl_desc_0 { unsigned long reserved; //今后版本升级所需的区域 unsigned long Block_size; //Block大小 } 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; } }
来看看构造函数的调用,因为转换较多,看起来比较复杂,以下去掉转换的部分:
//void (*blockName) (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* blockName = &tmp;
该源代码将__main_block_impl_0结构体类型的局部变量,即栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量
blockName
。这部分代码对应的最初源代码:
void (^blockName) (void) = ^{printf("Block\n");};
将Block语法生成的Block赋给Block类型变量blockName,它等同于将__main_block_impl_0结构体实例的指针赋给变量blockName。构造函数是C++中一种特殊的成员函数,用于在创建结构体对象时对其进行初始化操作,避免对象处于未定义状态。构造函数名称必须和类(包括结构体)的名称完全相同,无返回类型(包括void),若构造函数名称和结构体名不一致,编译器将不认为这是一个有效的构造函数,而是一个普通的成员函数。
-
下面来分析一下该构造函数
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)
中的参数:
第一个参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针:static struct __main_block_desc_0 __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) //Block大小 };
-
接下来来看看调用Block的部分:
blockName();
这部分源代码:((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
去掉转换部分:
(*blockName->impl.FuncPtr)(blockName);
可以看出这是简单的函数指针调用函数。
-
最后探究一下上面没有提到的
_NSConcreteStackBlock
isa = &_NSConcreteStackBlock;
首先要理解OC类和对象的实质,所谓Block就是Objective-C对象。
“id”这一变量类型用于存储OC对象,在usr/include/objc/runtime.h中是如下进行声明的:typedef struct objc_object { Class isa; }* id; typedef struct objc_class { Class isa; }* Class;
这两种结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。
下面通过编写OC类来确认一下:@interface MyObject : NSObject { int val0; int val1; } //基于objc_object结构体,该类的对象的结构体如下: struct MyObject { Class isa; int val0; int val1; }
MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。
各类的结构体就是基于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; unitptr_t data_NEVER_USE; };