博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Objective-C Runtime:类的基础数据结构
阅读量:4964 次
发布时间:2019-06-12

本文共 6743 字,大约阅读时间需要 22 分钟。

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

你可能感兴趣的文章
ecshop和ucenter的整合
查看>>
SQL中Group By的使用
查看>>
Python基础练习
查看>>
12 KLT算法
查看>>
fir.im Weekly - iOS/Android 应用程序架构解析
查看>>
堆表修改内幕
查看>>
egret之好友列表(滑动列表)
查看>>
树链剖分
查看>>
C语言中的setjmp和longjmp函数
查看>>
scala学习笔记3(trait)
查看>>
Apache Log4j使用实例
查看>>
Miller-Rabin判质数和Pollared-Rho因数分解
查看>>
Python-HelloWorld
查看>>
canvas 绘制图形
查看>>
【转】如何用Eclispe调试java -jar xxx.jar 方式执行的jar包
查看>>
线程中释放锁的方式
查看>>
hibernate hql limit的实现方式
查看>>
spring mvc controller中的参数验证机制(二)
查看>>
2017年秋季个人阅读计划
查看>>
利用图片代替按钮
查看>>