Objective-C Runtimeの类与对象
Objective-C是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这样处理也就意味着,它将使我们的代码更有灵活性。比如,我们可以根据我们的意向将消息转发给其它对象,或者去替换我们想要实现的方法等。因为Objective-C“动态化”的内容都是在运行时完成的,所以,OC的运行条件不仅仅要求有帮助我们向机器说话的编译器,还要有让代码随心而动的运行时,而这个运行时就是objc Runtime。它基本上是由C和汇编实现的,它让C具有了面向对象的能力。
Runtime库主要做下面几件事:
- 封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。
- 找出方法的最终执行代码:当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
类与对象
Objective-C是对C的进一步封装,让C有了面对对象的能力,为什么这么说呢,我们可以看一下下面的这个例子:
Person *per = [[Person alloc] init];
这是一个很简单的获取实例化对象的方法。
那么这个语句在经过编译后会变成什么样呢,想看的话我们可以这样做:
-
打开Xcode,创建一个命令行文件。
-
创建一个.m 文件,在里边定义我们想要的类, 内容如下所示:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p.age = 18;
NSLog(@"%ld", p.age);
}
return 0;
}
- 打开命令行,编译main.m为.cpp文件,打开文件可以看到main函数中的实现如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)p, sel_registerName("setAge:"), (NSInteger)18);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dd_c58dp2w556l44jqlhxdgthdh0000gn_T_main_449954_mi_0, ((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("age")));
}
return 0;
}
在以上函数中,采用了消息发送机制,可以看到,OC在这里也脱去了它面向对象的外衣,显露除了些许本质。
以实例化对象p的初始化过程为例,简要分析一下其实现过程:
首先,在原文件中的
Person *p = [[Person alloc]init];
先后调用了两个方法,也即发送了两个消息, 对应的,编译后文件就是实现这一过程。
Person *(*)(id, SEL)(void *)objc_msgSend((id)objc_getClass("Person"), sel_registerName("alloc"));
这一方法对应的就是Person调用的alloc方法,objc_msgSend函数发送alloc消息(需要强转),它将会返回一个结构体(Person的实例化对象)。同理,返回的对象在执行初始化方法init时,需要再次发送消息,也就有了在.cpp文件中我们看到的样子:
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
*注:在.m中如果也想如此实现,需要引用<objc/runtime.h>和<objc/message.h>
数据结构
Class
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。
查看objc/runtime.h中objc_class结构体的定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
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;
MetaClass
在objc_class的结构体中有isa这么一个字段,在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)。
当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。
meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。
通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下图所示:
我是这样去理解这个图的:
图中的各个类的关系以及isa指向就如同一个老亚家的家谱以及财产关系,首先图中的subClass可以看做是以诺, 他管理着一个村落中的所有兵器(instance of Class的方法),当村民需要使用时,需要问自己的村长有没有,并在有的情况下调用,如果没有呢?当你要去干掉一条龙,意气风发的去跟村长要剑时,发现他竟然没有,当时你就要崩溃,村长一看,“哎哟哟,你可别死我家门口,我问问我老爹有没有”,然后就给该隐打电话,向他借,再不行就让该隐向亚当借,这要是都没有。。以诺就会告诉村民“行了,大家一起死吧,你要的东西大家都没有”。然后全世界就崩溃掉了(野指针:指向了未识别的方法)。
这时候你就要问了,那subclass(meta)又是何方神圣呢?好吧,我也不知道她叫啥,是以诺的媳妇就是了,她藏着以诺的小金库,里边装着村长能用的兵器(类方法),当村长要用时就得向她申请,同理,她没有就得向她妈要,在这里因为女子无需上战场,所以她们本身要兵器。。你猜她们想干嘛,所以她们想要兵器直接向RootClass(meta)夏娃说句悄悄话, 夏娃想要兵器也没地要去,看看自己有啥吧。秉承着男传男,女传女的原则,可以很清楚的看清图中的继承关系。等等,天呐,RootClass(meta)继承于RootClass,这是咋回事?嘿嘿,别忘了,夏娃是用亚当的一根肋骨造出来的。而亚当,你能跟造出他那位打通电话吗?