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);
复制代码