本文为通识性的文章,并且会不断更新,如果有什么地方不懂的或者有需要补充的以及不太正确之处可以留言,我会补上的,谢谢各位!~
OC是一门动态的语言,它将很多静态语言在编译和链接时期做的事情放到了运行时来处理。这样的动态语言优势在于:写代码时更具灵活性。
e.g. 我们可以把消息转发给我们想要的对象,或者随意更换一个方法的实现等。
OC的这种特性使得他不仅仅需要一个编译器,还需要一个运行时系统来执行编译的代码,这个运行时系统被称为:Objc Runtime
。Objc Runtime
事实上是一个Runtime
库,基本上是使用C和汇编写的。因为这个库,他使得C语言有了面向对象的能力。
- 封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数实现。这些结构体和函数被Runtime函数封装之后,我们就可以在程序运行时进行创建、检查,修改类、对象和他们的方法了。
- 找出方法的最终执行代码: 当程序执行(object do something)时,会向消息接受者(object)发送一条消息(do something),runtime会根据消息接受者是否能响应该消息而做出不同的反应。这将在后面详细介绍。
OC的类是由Class类型来表示的,事实上,这是一个指向objc_class
的结构体的指针。它的定义如下:
typedef struct objc_class *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(元类)
;methodList
中遍历一遍,这种做法的性能肯定不好。这时,cache就可以很好的解决这个问题。当程序每调用一次方法时,现在cache中进行查找,如果cache中没有这个方法,再去找methodList
中的信息,并且存入cache中。这样,在下次调用的时候runtime就会优先去cache中查找。这样经常用到的方法,就可以高效率的进行使用。以下,我们来举个栗子说明其执行的过程:
NSArray *array = [[NSArray alloc] init];
这是一句很简单的创建array的语句,这句语句被执行的流程为:
[NSArray alloc]
先执行,但是NSArray这个类中没有alloc方法,于是到父类NSObject
中查找此类。- 检测到
NSObject
是否可以相应+ (instancetype)alloc
方法。一旦发现响应,就会检测NSArray类,根据所需要的空间大小分配内存空间,把isa指针指向NSArray
类。- 将
alloc
类加入到cache列表中。- 后期操作时,如再遇到
[[NSArray alloc] init]
,就会直接从cache中取出响应的方法。
objc_object
表示一个类的实例的结构体,它的定义如下objc/objc.h
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
这个结构体只有指向类的isa指针,这样,当我们向一个OC对象发送消息时,运行时库就会根据实力对象的isa指针找到实力对象所属的类。Runtime库就会在类的方法列表,以及父类的方法列表中去寻找与消息对应的selector
指向的方法。找到后就可以运行这个方法。
因此,我们知道,当创建一个特定类的实力对象时,它事实上就是一个objc_object
的类型的指针。这样的存在可以让我们实现类似于C++中泛型的一些操作。和java这样的强类型不同的是,该类型的对象可以转换为任何一种对象,有点类似于c语言中void *
指针类型的作用。
objc_cache
我们知道,cache是用来缓存之前调用过的方法,这个字段是指向objc_cache
结构体的指针。
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
bucket
的总数。OC的runtime使用mask来确定从哪里开始线性查找,也就是说,这个是记录一个索引位置的字段。 事实上,指向方法selector
的指针与该字段做一个按位与操作(index = (mask & selector))。他可以作为简单的hash散列算法。bucket
总数。在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(实际操作中,就是调用类方法)
NSArray *array = [NSArray array];
从之前的分析中,我们可以可知道,这个+ array
消息是发送给NSArray
中,而这个NSArray
事实上也是一个对象。
既然是一个对象,那么就会有objc_object
指针,它包含一个指向类的isa指针。
那么,这个指针指向的是什么?
为了能够调用+ array
的方法,这个类的isa指针必须指向一个包含这些类方法的objc_class的结构体。这个被指向的类,就是meta-class。
!!meta-class是一个类对象的类。
当向一个对象发送消息时,runtime会在对象所属的类方法列表中查找方法。
当向一个类发送对象时,runtime会在类的元类的方法列表中查找。
meta-class中之所以重要,是因为meta-class存储着一个类的所有类方法。没各类都有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
事实上,meta-class也是一个类,也可以向它发送消息。(即调用类方法)
那么,meta-class的isa指向什么呢?
OC的设计者让所有的meta-class的isa指向基类的meta-class。
任何
NSObject
继承体系下的meta-class
都使用NSObject
的meta-class
作为自己的所属类,基类的meta-class
的isa指向自己。
这里需要注意的是:我们在一个类对象调用class方法是无法获取meta-class,它只是返回类而已。
runtime提供了大量的函数操作类与对象。
类的操作方法大部分是以class为前缀。
对象的操作方法大部分是以objc或object_为前缀。
类名操作的函数主要有:
// 获取类的类名
const char * class_getName ( Class cls );
// 获取类的父类
Class class_getSuperclass ( Class cls );
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
// 获取实例大小
size_t class_getInstanceSize ( Class cls );
在objc_class中,所有的成员变量、属性的信息是放在链表ivars中的。ivars是一个数组,数组中的每个元素都是指向Ivar(变量信息)的指针。runtime提供了丰富的函数来操作这一字段。
/*
获取类中指定名称实例成员变量的信息
它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)
*/
Ivar class_getInstanceVariable ( Class cls, const char *name );
/*
获取类成员变量的信息
注意,返回的列表不包含父类的成员变量和属性。
*/
Ivar class_getClassVariable ( Class cls, const char *name );
/*
添加成员变量
这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。
这个类也不能是元类。
*/
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
/*
获取整个成员变量列表
返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。
outCount指针返回数组的大小。
必须使用free()来释放这个数组
*/
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );
// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
/*
添加方法
会覆盖父类的方法实现,但不会取代本类中已存在的实现
如果本类中包含一个同名的实现,则函数会返回NO。
*/
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
/*
获取实例方法
会包含父类的方法
*/
Method class_getInstanceMethod ( Class cls, SEL name );
/*
获取类方法
会包含父类的方法
*/
Method class_getClassMethod ( Class cls, SEL name );
/*
获取所有方法的数组
不会包含父类的方法
*/
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
/*
替代方法的实现
如果类中不存在name指定的方法,那么就类似于class_addMethod函数
如果类中已存在name指定的方法,就会类似于method_setImplementation
*/
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。
// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
/*
返回类是否实现指定的协议
可以使用NSObject类的conformsToProtocol:方法来替代
*/
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
/*
返回类实现的协议列表
在使用后我们需要使用free()手动释放
*/
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
// 获取版本号
int class_getVersion ( Class cls );
// 设置版本号
void class_setVersion ( Class cls, int version );
/*
创建一个新类和元类
如果我们要创建一个根类,则superclass指定为Nil。
extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
*/
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
/*
销毁一个类及其相关联的类
如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法
*/
void objc_disposeClassPair ( Class cls );
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls );
/*
创建类实例
extraBytes参数表示分配的额外字节数。
**该函数在ARC环境下无法使用。**
*/
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 释放指定对象占用的内存
id object_dispose ( id obj );
有这样一种场景,假设我们有类A和类B,且类B是类A的子类。类B通过添加一些额外的属性来扩展类A。现在我们创建了一个A类的实例对象,并希望在运行时将这个对象转换为B类的实例对象,这样可以添加数据到B类的属性中。这种情况下,我们没有办法直接转换,因为B类的实例会比A类的实例更大,没有足够的空间来放置对象。此时,我们就要以使用以上几个函数来处理这种情况。
// 修改类实例的实例变量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 获取对象实例变量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向给定对象分配的任何额外字节的指针
void * object_getIndexedIvars ( id obj );
// 返回对象中实例变量的值
id object_getIvar ( id obj, Ivar ivar );
// 设置对象中实例变量的值
void object_setIvar ( id obj, Ivar ivar, id value );
// 返回给定对象的类名
const char * object_getClassName ( id obj );
// 返回对象的类
Class object_getClass ( id obj );
// 设置对象的类
Class object_setClass ( id obj, Class cls );
// 获取已注册的类定义的列表
int objc_getClassList ( Class *buffer, int bufferCount );
// 创建并返回一个指向所有已注册类的指针列表
Class * objc_copyClassList ( unsigned int *outCount );
// 返回指定类的类定义
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
// 返回指定类的元类
Class objc_getMetaClass ( const char *name );