前情提要
1.闭包、Block是一个带有自动变量值(可以截获自动变量值)的匿名函数。截获的含义是保存该自动变量的瞬间值。
2.OC中如果要改变Block截获的外部自动变量的值,需要在该变量前加上__block
修饰符。Swift不用,系统会自动处理这些问题。延伸到对象,对于Swift来说系统也会处理但OC不同。OC下,当从外部截获一个对象(NSMutableArray* )mArray
时,在Block内调用NSMutableArray
的方法(addObject:)
没问题,因为Block捕获的是NSMutableArray
类对象用的结构体实例指针。但若对其进行操作,比如赋值,就会产生编译错误,要在变量前加上__block
。Swift下直接用就行了。
3.从C代码的角度分析closure/block
。OC可以直接用clang -rewrite-objc 原文件名
来分析,Swift下的clang ..
我没有找到对应的命令,所以只看OC的。在分析之前,先介绍两个小概念。
--1.OC&Swift中的self
OC Code:
- (void) method:(int)arg {
NSLog(@"%p %d\n", self, arg);
}
MyObject *obj = [[MyObject alloc] init];
[obj method: 10];
转换成C Code:
void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg) {
NSLog(@"%p %d\n", self, arg);
}
MyObject *obj = objc_msgSend(objc_getClass("MyObject"), sel_registerName("alloc"));
obj = objc_msgSend(obj, sel_registerName("init"));
objc_msgSend(obj, sel_registerName("method:"), 10);
最后一条执行语句,也就是[obj method: 10]
转换成objc_msgSend(obj, sel_registerName("method:"), 10)
。objc_msgSend(obj, sel_registerName("method:"), 10)
函数根据指定的对象和函数名,从对象持有类(MyObject)的结构体中检索[XX method:10]
对应的函数-->void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)
的指针并调用。此时,objc_msgSend函数的第一个参数obj
作为_I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)
的第一个参数self
进行传递。即self
就是Myobject
类的对象-obj
自己。
--2.OC中的id
*id为objc_object结构体的结构体指针。
id在C语言中的声明:
typedef struct objc_object {
Class isa;
} *id;
*Class为objc_class结构体的结构体指针。
Class在C语言中的声明:
typedef struct objc_class {
Class isa;
} *Class;
这与objc_object结构体相同。然而,objc_object结构体和objc_class结构体归根结底是在各个对象(id)和类(Class)的实现中使用的最基本的结构体。
例:
类,MyObject
@interface MyObject : NSObject {
int val0;
int val1;
}
@end
使用MyObject
创建的对象newObject = [MyObject new]
,就是基于objc_object
创建了一个该类(MyObject)的结构体实例。通过isa保持该类(MyObject)的结构体实例指针。
struct newObject {
Class isa;
int val0;
int val1;
...;
}
而各类的结构体是就是基于objc_class的 class_t结构体。
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
...;
};
class_t中持有,声明的成员变量,方法的名称,方法的实现(函数指针),属性以及父类的指针,并被OC运行时库所使用的。
对象中的isa指针和类中的isa指针的区别在于,对象的是Class类型,只负责指向他的所属类。类的isa指针是class_t类型,包含了class_t结构体中的所有内容(比如方法,父class等等),指向它自己的元类(metaclass)。他的元类中就包含了各种方法,变量等等。而他的metaclass的isa指向了NSObject,superclass指向它父类的元类。以ViewController举例:
static void OBJC_CLASS_SETUP_$_ViewController(void ) {
OBJC_METACLASS_$_ViewController.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_ViewController.superclass = &OBJC_METACLASS_$_UIViewController;
OBJC_METACLASS_$_ViewController.cache = &_objc_empty_cache;
OBJC_CLASS_$_ViewController.isa = &OBJC_METACLASS_$_ViewController;
OBJC_CLASS_$_ViewController.superclass = &OBJC_CLASS_$_UIViewController;
OBJC_CLASS_$_ViewController.cache = &_objc_empty_cache;
}
给ViewController添加了一个实例方法(instanceFunc
)一个类方法(classFunc
)。
Metaclass方法列表的赋值:(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_ViewController;
--->OBJC_$_CLASS_METHODS_ViewController
--->:
_OBJC_$_CLASS_METHODS_ViewController __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"classFunc", "v16@0:8", (void *)_C_ViewController_classFunc}}
};
实例方法被保存在了_class_ro_t _OBJC_CLASS_RO_$_ViewController
这里他会赋值给_class_t OBJC_CLASS_$_ViewController
也就是本类。
即元类保存了类方法。
本类保存了实例方法。
MyObject类的实例变量val0,val1被直接声明为对象的结构体成员。OC中由类生成对象就意味着,生成一个基于该类的结构体实例,这些结构体实例(对象)通过isa保持该类的结构体实例指针。举个例子就是:有3个类,C动物->C狗->C柯基,基于柯基生成的一个对象叫做KK,那么KK这个结构体就会有一个isa指向C柯基,而这个C柯基的isa指向自己的元类。当KK发送了一个消息(狼嚎时)因为C柯基本类的class_t结构体中的cache和vtable都没有这个方法,便逐级会向上传递,找不到再performslector等等。
介绍完两个小概念,开始从C代码的角度分析Block,举两个例子,一个是不截获自动变量值的另一个是截获自动变量值的:
不截获自动变量值:
.m文件下的代码:
int main() {
void(^blk)(void) = ^{printf("Block\n");};
blk();
return0;
}
clang -rewrite-objc 项目文件名.m之后,摘取有用的内容:
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 {
size_t reserved;
size_t Block_size;
}
__main_block_desc_0_DATA= {0, sizeof(struct __main_block_impl_0)};
int main() {
void(*blk)(void) = ((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);
return0;
}
源码中的^{printf("Block\n");};
转换后的代码是
static void__main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}
通过转换后的代码可知,通过Block使用的匿名函数实际上被作为简单的C语言函数来处理。另外根据Block语法所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名(void__main_block_func_0
)。该函数的参数*__cself
就是指向Block值的变量,__main_block_impl_0
结构体的指针。
那我们来看看__main_block_impl_0
结构体,去掉构造函数后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
第一个成员变量是impl
,他的结构体声明是:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
impl
是虚函数列表,该结构体内存放了一个标志,包括今后版本升级所需的区域及函数指针。
第二个成员变量是Desc
指针,__main_block_desc_0
结构体的声明是:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
这些也如同其成员名称所示,其结构为今后版本升级所需的区域和Block大小。
这两个成员变量看完之后,再来看一下初始化含有这些结构体的__main_block_impl_0
结构体的构造函数,就是我们刚才忽略的那一部分代码,这个代码执行完成意味着_cself
初始化完成:
__main_block_impl_0(void *fp,struct__main_block_desc_0 *desc,intflags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
在分析这个结构体之前,先看一下这个构造函数的调用(在 int main() {...}
中):
void(*blk)(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 *blk = &tmp;
该源代码将__main_block_impl_0
结构体类型的自动变量(栈上生成的__main_block_impl_0
结构体实例的指针),赋值给__main_block_impl_0
结构体指针类型的变量blk,对应未转换前的代码就是这个赋值操作:
void(^blk)(void) = ^{printf("Block\n");};
即,将Block语法生成的Block赋给Block类型变量blk。该源代码中的Block就是__main_block_impl_0
结构体类型的自动变量,即栈上生成的__main_block_impl_0
结构体实例。然后指针赋值。
接下来看看__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
结构体实例构造参数。__main_block_func_0
和&__main_block_desc_0_DATA
。
__main_block_func_0
是一个由Block语法转换的C语言函数指针,对应的函数就是:static void __main_block_func_0(struct__main_block_impl_0 *__cself) {printf("Block\n");}
&__main_block_desc_0_DATA
是作为静态全局变量初始化的__main_block_desc_0
结构体实例指针。其初始化代码如下:
__main_block_desc_0_DATA= {0,sizeof(struct__main_block_impl_0)};
由此可知,该源代码使用Block,即__main_block_impl_0
结构体实例的大小,进行初始化。
下面综合来看一下,栈上的Block即__main_block_impl_0
结构体实例的初始化到底是怎样完成的(相关参数展开):
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
函数指针赋值给成员变量FunPtr
,为后续调用做好准备。
源码中调用blk
的部分blk()
对应转换后的代码是:
((void(*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉转换部分:
(*blk->impl.FuncPtr)(blk);
这就是简单地使用函数指针调用函数。
阶段总结
由Block语法转换的__main_block_func_0
函数的指针被赋值给栈上生成的__main_block_impl_0
结构体实例的FuncPtr中,再将栈上生成的__main_block_impl_0
结构体实例赋值给Block类型的变量blk
,之后调用blk()
。
通过观察调用函数,__main_block_func_0函数的参数__cself指向Block值blk
,调用:(*blk->impl.FuncPtr)
,参数:(blk)
。由此看出Block正是作为参数进行了传递。
截获自动变量值的:
.m文件下的代码:
#include <stdio.h>
int main() {
int unCapturedVariable = 100;
int capturedVariable = 60;
const char *fmt = "capturedVariable = %d/n";
void(^blk)(void) = ^{printf(fmt, capturedVariable);};
blk();
return 0;
}
clang -rewrite-objc 项目文件名.m之后,摘取有用的内容:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int capturedVariable;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _capturedVariable, int flags=0) : fmt(_fmt), capturedVariable(_capturedVariable) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}
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 unCapturedVariable = 100;
int capturedVariable = 60;
const char *fmt = "capturedVariable = %d/n";
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
这与前面转换的源代码稍有差异。下面来看看不同之处。首先我们注意到,Block语法表达式中使用的自动变量(capturedVariable
和fmt
)被作为成员变量追加到了__main_block_impl_0
中。并且类型完全相同,而Block语法表达式中没有使用的自动变量(unCapturedVariable
)并没有被追加进去。
因此,Block的自动变量截获只针对Block中使用的自动变量。
再来看看初始化__main_block_impl_0
结构体的构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _capturedVariable, int flags=0) : fmt(_fmt), capturedVariable(_capturedVariable) {...}
,有什么不同。
在初始化结构实例时,根据传递给构造函数的参数对成员变量(capturedVariable和fmt)进行初始化。通过以下代码:
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
。使用执行Block语法时的自动变量capturedVariable
和fmt
来初始化__main_block_impl_0
结构体实例(系统生成的Block,或者叫右边的Block可能好理解一些)。即在该源代码中,__main_block_impl_0
结构体实例的初始化方法如下(相关参数展开):
struct __main_block_impl_0 {
void *isa;
Int Flags;
void &FuncPtr;
struct __main_block_desc_0* Desc;
char *fmt;
int capturedVariable;
}
赋值:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
fmt = "capturedVariable = %d/n";
capturedVariable = 60;
由此可知在__main_block_impl_0
结构体实例中,成员变量(fmt
和capturedVariable
)被外部自动变量值赋值。
下面再来看一下使用Block的匿名函数的实现。转换前的代码片段是:printf(fmt, capturedVariable);
转换后的是:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}
在转换后的源代码中,截获到__main_block_impl_0结构体实例的成员变量的自动变量(__cself->fmt
和__cself->capturedVariable
), bound by copy!这些变量(fmt
和capturedVariable
)在Block语法表达式执行之前(没初始化__main_block_impl_0
之前)就已经被声明定义在__main_block_impl_0
结构体里。
网友评论