Objective-C Runtime:类的基础数据结构

Runtime简介

Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime系统来动态创建类和对象,进行消息发送和转发。 Runtime库主要做下面几件事:

1、封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。
2、找出方法的最终执行代码:当程序执行[object doSomething]时,会向消息接收者object发送一条消息doSomethingruntime会根据消息接收者是否能响应该消息而做出不同的反应。

可以在这里下载Apple维护的开源代码。

方法调用的本质

Objective-C中形如[receiver message]的方法调用,并不是直接调用receivermessage方法,而是向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_objectisa指针的数据类型是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一样有自己的isasuper_class指针,关系如下:

上图的实线是 super_class指针,虚线是 isa指针,有几个关键点的解释如下:

1、Root class(class)其实就是NSObjectNSObject没有超类,所以Root class(class)superclass指向nil
2、每个Class都有一个isa指针指向唯一的Meta Class
3、Root class(meta)superclass指向Root class(class),也就是NSObject,形成一个回路;
4、每个Meta Classisa指针都指向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_ivarobjc_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时,首先根据receiverisa指针查找它对应的类,然后在类的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);
复制代码

参考

转载于:https://juejin.im/post/5d2164836fb9a07eca699d2c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值