Runtime简介
Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime系统来动态创建类和对象,进行消息发送和转发。
Runtime库主要做下面几件事:
1、封装:在这个库中,对象可以用
C语言中的结构体表示,而方法可以用C函数来实现,另外再加上一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。
2、找出方法的最终执行代码:当程序执行[object doSomething]时,会向消息接收者object发送一条消息doSomething,runtime会根据消息接收者是否能响应该消息而做出不同的反应。
可以在这里下载Apple维护的开源代码。
方法调用的本质
Objective-C中形如[receiver message]的方法调用,并不是直接调用receiver的message方法,而是向receiver发送一条message消息,该方法调用代码会被编译成:
id objc_msgSend(id self, SEL selector, ...);
id
id是函数objc_msgSend第一个参数的数据类型,id是通用类型指针,可以表示任何对象,在<objc.h>中有其定义:
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
id其实就是一个指向objc_object结构体的指针,它包含一个Class成员,根据isa就能找到对象所属的类。
SEL
SEL是函数objc_msgSend第二个参数的数据类型,又叫方法选择器,是表示一个方法的selector的指针,在<objc.h>中有其定义:
typedef struct objc_selector *SEL;
关于objc_selector在源码中并没有找到其定义,其实它就是映射到方法的C字符串,你可以通过Objc编译器命令@selector()或Runtime系统的sel_registerName函数来获取一个SEL类型的方法选择器。
如果你知道selector对应的方法名是什么,你可以通过NSString *NSStringFromSelector(SEL aSelector);方法将SEL转化成字符串,再用NSLog打印。
IMP
IMP实际上是一个函数指针,指向方法实现的首地址。在<objc.h>有其定义:
typedef id (*IMP)(id, SEL, ...);
第一个参数指向self的指针(如果是实例方法,则是类实例的内存地址,如果是类方法,则是元类的内存地址),第二参数是方法选择器,接下来是方法的实际参数列表。
前面介绍的SEL就是为了查找方法的最终实现IMP的。每个方法对应唯一的SEL,我们可以通过SEL方便快速准确地获取它的IMP。
Class的数据结构
结构体objc_object的isa指针的数据类型是Class,表示其所属的类,在<objc.h>中有其定义:
typedef struct objc_class *Class;
可以看到Class其实就是指向objc_class的结构体指针,在<runtime.h>中有其定义:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class;// 指向其父类
const char *name;// 类名
long version;// 类的版本信息,初始化默认为0
long info;// 一些标识信息,如CLS_CLASS (0x1L)表示该类为普通类,包含对象方法和成员变量;CLS_META (0x2L)表示该类为元类,包含类方法
long instance_size;// 该类的实例变量大小(包括从父类继承下来的实例变量)
struct objc_ivar_list *ivars;// 用于存储每个成员变量的地址
struct objc_method_list **methodLists;// 方法列表,与info的标识位有关,如果是CLS_CLASS (0x1L)则存储对象方法;如果是CLS_META (0x2L)则存储类方法
struct objc_cache *cache;// 指向最近使用的方法的指针,用于提升效率
struct objc_protocol_list *protocols;// 用于存储该类遵守的协议
#endif
}
下面对类结构的各个成员进行介绍,包括与之相关的操作函数:
isa
isa表示Class对象的Class,也就是Meta Class。在面向对象的设计中,一切都是对象,Class在设计中也是一个对象。当我们向一个对象发消息时,runtime会在这个对象所属的类的方法列表中进行查找,而向一个类发送消息时,会在这个类的Meta Class的方法列表中进行查找。Meta Class也是Class,它也跟其它Class一样有自己的isa和super_class指针,关系如下:
super_class指针,虚线是isa指针,有几个关键点的解释如下:
1、
Root class(class)其实就是NSObject,NSObject没有超类,所以Root class(class)的superclass指向nil;
2、每个Class都有一个isa指针指向唯一的Meta Class;
3、Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路;
4、每个Meta Class的isa指针都指向Root class(meta)。
/* 判断一个类是否是元类 */
BOOL class_isMetaClass(Class cls);
/* 获取指定类的元类 */
Class objc_getMetaClass(const char *name);
super_class
类的父类,如果该类已经是根类,则为NULL。
/* 获取指定类的父类,通常我们直接调用NSObject对象的superclass方法代替该函数 */
Class class_getSuperclass(Class cls);
name
类名。
/* 返回类的名称 */
const char *class_getName(Class cls);
version
类的版本信息,默认为0。
/* 返回类的版本信息 */
int class_getVersion(Class cls);
info
供运行期使用的一些标识。如CLS_CLASS (0x1L)表示该类为普通类;CLS_META (0x2L)表示该类为元类。
instance_size
该类的实例变量大小,包括从父类继承下来的实例变量。
/* 返回该类的实例变量大小 */
size_t class_getInstanceSize(Class cls);
ivars
表示成员变量,它指向objc_ivar_list结构体,在<runtime.h>中有其定义:
struct objc_ivar_list {
int ivar_count;
#ifdef __LP64__
int space;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1];
}
objc_ivar_list其实就是一个链表,存储多个objc_ivar,objc_ivar结构体存储类的单个成员变量信息,在<runtime.h>中定义如下:
struct objc_ivar {
char *ivar_name;// 变量名
char *ivar_type;// 变量类型
int ivar_offset;
#ifdef __LP64__
int space;
#endif
}
另外,在<runtime.h>中还定义了Ivar,它是指向objc_ivar的结构体指针:
typedef struct objc_ivar *Ivar;
/* 获取整个成员变量列表。
返回结果是一个Ivar类型的指向类的成员变量的指针数组,不包括父类的成员变量,使用完毕之后必须手动调用free()来释放数组。
*/
Ivar *class_copyIvarList(Class cls, unsigned int *outCount);
/* 获取指定名称的成员变量 */
Ivar class_getInstanceVariable(Class cls, const char *name);
/* 给指定的类添加成员变量。
1.该函数只能在objc_allocateClassPair和objc_registerClassPair函数之间调用,且不允许往一个已经存在的类中添加实例变量。
2.这个类不能是元类。
*/
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types);
methodLists
表示方法列表,它是指向objc_method_list结构体的二级指针,可以动态的修改*objc_method_list的值来添加方法,也是Category的实现原理。在<runtime.h>中查看objc_method_list结构体的定义如下:
struct objc_method_list {
struct objc_method_list *obsolete;
int method_count;
#ifdef __LP64__
int space;
#endif
/* variable length structure */
struct objc_method method_list[1];
}
同样,objc_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息。在<runtime.h>中有其定义:
struct objc_method {
SEL method_name;// 方法名
char *method_types;
IMP method_imp;// 方法的实现
}
另外,<runtime.h>中还定义了Method,它是指向结构体objc_method的结构体指针,定义如下:
typedef struct objc_method *Method;
关于方法的主要操作函数:
/* 给类添加方法。
1.class_addMethod会覆盖父类方法的实现,但不会取代本类中已存在的实现,如果本类中含有一个同名实现,则函数返回NO;
2.如果要修改已存在的实现,可以使用method_setImplementation。
*/
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
/* 获取实例方法
该函数会搜索父类的实现,而class_copyMethodList不会。
*/
Method class_getInstanceMethod(Class cls, SEL name);
/* 获取类方法
该函数会搜索父类的实现,而class_copyMethodList不会。
*/
Method class_getClassMethod(Class cls, SEL name);
/* 获取方法列表
1.该函数不会搜索父类的实现;
2.函数调用结束后要手动调用free();
*/
Method *class_copyMethodList(Class cls, unsigned int *outCount);
/* 替代指定方法的实现
该函数有两种行为:
1.如果指定name的方法不存在,则类似于函数class_addMethod一样添加方法;
2.如果指定name的方法存在,则类似于函数method_setImplementation,替代原先方法的实现。
*/
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
/* 获取方法的具体实现,该函数在向类实例发送消息时会被调用,返回一个指向方法实现的函数指针
1.比函数method_getImplementation(class_getInstanceMethod(cls, name))更快;
2.返回的函数指针可能是一个指向runtime的内部函数,而不一定是方法的实际实现。比如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发的一部分。
*/
IMP class_getMethodImplementation(Class cls, SEL name);
/* 类实例是否响应指定的方法
通常我们应该使用NSObject对象的respondsToSelector:或者instancesRespondToSelector:来达到相同的目的。
*/
BOOL class_respondsToSelector(Class cls, SEL sel);
cache
用于缓存最近使用的方法。cache是指向objc_cache结构体的指针,在<runtime.h>中有如下定义:
typedef struct objc_cache *Cache;
struct objc_cache {
unsigned int mask /* total = mask + 1 */;
unsigned int occupied;
Method buckets[1];
};
Cache其实是一个存储Method的链表,主要是为了优化方法调用的性能。当对象recevier调用方法message时,首先根据receiver的isa指针查找它对应的类,然后在类的methodLists中查找方法,如果没找到,就会用super_class指针到父类的methodLists中查找,一旦找到就调用。如果没找到,有可能消息转发,也可能忽略它。但这样的查找方式效率太低,因为一个类只有少部分的方法会被经常调用,一些方法很少调用甚至不会被调用,所以使用Cache来缓存调用方法,当调用方法时,优先在Cache中查找,如果没找到再到methodLists中查找。
protocols
类遵循的协议,它是指向objc_protocol_list结构体的指针,在<runtime.h>查看其定义:
struct objc_protocol_list {
struct objc_protocol_list *next;
long count;
Protocol *list[1];
};
/* 返回类实现的协议列表 */
Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount);
/* 类是否实现指定的协议
通常使用NSObject对象的conformsToProtocol:来实现相同的目的。
*/
BOOL class_conformsToProtocol(Class cls, Protocol *protocol);