坚持 成长 每日一篇
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时能够更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。(个人认为这样有优点也有缺点,就是在很多错误会在运行时候才被发现,与C#等可以在编译时候就发现程序的错误,OC这个也算是缺点吧)
OC通过一个运行时系统即runtime来实时执行编译的动作,runtime系统就像一个守护进程一样保证程序运行。runtime是一个C和汇编写成的库,使C语言具有面向对象的能力。
Runtime使用C语言结构体表示对象,用C语言函数表示方法,这些C语言函数和结构体被Runtime封装后,我们就可以在程序中执行创建,检查,修改类和对象和他们的方法。
当程序执行时,会向程序接收者发送(object)发送一条消息,runtime根据接收者是否响应该消息做出不同的响应。
Objective-C runtime目前有两个版本:Modern runtime(64位版本)和Legacy runtime(32位版本,可以忽略了)。
OC的类
OC的Class其实是一个objc_class结构体的指针,下面是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
}
注意:由此可见在OC中类其实就是一个C结构体指针,又因为Runtime用C结构体表示对象,我们可以认为类本身就是一个对象。
下面介绍该结构体的一些属性:
isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)。
注意:我们可以认为所有类都有元类,所有元类的元类指向根类的元类(即根元类)。根元类的元类指向自身。
super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。根元类的super_class指向根类,类的元类的super_class指向父类的元类。
version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
cache:用于缓存最近使用的方法。用户调用方法时候会先遍历cache里面的方法再遍历methodLists里的方法。一个方法调用时候,如果不存在cache里,而是在methdLists里被找到,在这个方法调用结束时候会被保存到cache里。这样子就实现了常用方法不需要去遍历list,以便提高效率。
对象和id类
OC里的每一个对象都是用objc_object结构体来表示,定义如下
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到,这个结构体只有一个字体,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后即运行这个方法。
id类型的定义
typedef struct objc_object *id;
这就是为什么id类型能指向任意对象的原因。
结构体objc_cache
下面是objc_cache结构体的定义
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。
occupied:一个整数,指定实际占用的缓存bucket的总数。
buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。
元类(MetaClass)
在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。如:
NSArray *array = [NSArray array];
类既然是一个对象,那么他也就是一个objc_object指针,那么他的isa指针指向的是该类的元类。元类的存在就是保存该类的类方法。让类也能像对象一样被反问。
下面是元类和常规类关系图
类与对象操作函数
runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。
获取该类元类方法
OBJC_EXPORT Class objc_getClass(const char *name)//因为者里把类当对象所以是以objc开头
例子:
Class currentClass = objc_getClass((__bridge void*)[NSObject class])
获取类的名字
OBJC_EXPORT const char *class_getName(Class cls)
例子:
NSString *classString = [NSString stringWithUTF8String:class_getName(objc_getClass((__bridge void*)[NSArray class]) )] ;
获取类的父类
class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。
Class class_getSuperclass ( Class cls );
判断给定的Class是否是一个元类
class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
BOOL class_isMetaClass ( Class cls );
获取实例大小
size_t class_getInstanceSize ( Class cls );