Runtime简介
Objective-C
是基于C
语言加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime
系统来动态创建类和对象,进行消息发送和转发。 Runtime
库主要做下面几件事:
1、封装:在这个库中,对象可以用
2、找出方法的最终执行代码:当程序执行C
语言中的结构体表示,而方法可以用C
函数来实现,另外再加上一些额外的特性。这些结构体和函数被runtime
函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。[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、
2、每个Root class(class)
其实就是NSObject
,NSObject
没有超类,所以Root class(class)
的superclass
指向nil
;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);复制代码