对于Objective-C runtime的浅薄认知(一)

其实我也不知道怎么开头, 那么就直接进入正题吧, runtime是Objective-C动态语言特性的体现, 在实际编码的过程中我们运用最多的就是category中动态创建属性, Method Swizzling, 下面我们从类和对象开始一步一步的了解runtime。

屏幕快照 2016-06-08 上午11.49.11.png

上面这段话出自Objective-C Runtime Programming Guide, 对于runtime, 有两个版本, 一个叫做“modern”, 一个叫做“legacy”。“modern”版本是以OC 2.0为基础,包括许多新的runtime特性;而“legacy”版本则是涉及OC 1为基础, 我们可以不必理会。

类和对象的基础数据结构

Class 类

屏幕快照 2016-06-08 下午2.12.21.png

首先, 我们来分析一下Class的结构体定义
1.isa: 在OC中, 类也是对象, 而既然是对象, 则该对象的isa指针指向的地址则是metaClass(元类), 对于metaClass, 下面会有图来介绍, 这里理解类是对象。
2.super_class:指向该类的父类, 当该类已经是NSObject等最顶层的类, 那么该类的super_class为NULL。
3.name:类名,及该类的名称, 如创建了一个叫MyClass类继承NSObject, 则该类的name为MyClass。
4.instance_size:该类实例变量的大小, 可以通过调用class_getInstanceSize(Class cls)方法来得到其值。
5.ivars:实例变量列表, 该列表以链表的形式返回,通过节点找到对应的实例变量所在的位置。
6.methodLists:方法链表, 包括类方法和对象方法。
7.cache:顾名思义, cache是缓存的意思, 但是此处的cache是对方法的缓存, 当接收者接收到一个消息是, 首先会根据isa指针去寻找能够响应该方法的对象, 当调用该方法后, 调用的方法会缓存到cache中, 下次调用的时候会先从cache中查找该方法, 如果该方法不存在, 再去methodLists中查找方法, 这样大大的提高的程序运行的效率。

例如: UILabel *label = [[UILabel alloc] init];
1.创建label对象, 首先调用+alloc的方法, 因为UILabel没有+alloc方法, 所以去父类UIView中找,没有再到UIResponder, 最后在NSObject中找到了+alloc方法, 之后会给UILabel分配内存空间, 同时+alloc方法存到cache中。
2.之后同样的方法执行init方法, 同样,-init方法被存到cache中。
3.如果再通过该方式创建UILabel对象, 则会先去cache中查找方法, 不必一直去method_lists中查找。

objc_object与id


屏幕快照 2016-06-08 下午3.29.06.png

对于对象来说, 其结构体中只有一个isa指针, 指向其类, 当runtime运行objc_msgSend方法的时候, 去方法列表中寻找能够响应的方法, 运行方法。

当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。

对于我们常见的id, 它是一个objc_object结构类型的指针, id类型可以转化成任意类型, 类似于C中的void *指针的作用。

元类(Meta Class)


屏幕快照 2016-06-08 下午3.39.18.png

想必了解runtime的人对于这张图应该不陌生了, 这张图很好的解释了meta class。
我们先看Subclass的部分, instance of Subclass, 初始化实例对象, 实例对象及为objc_object结构体, 实例对象的isa指针会指向类, 而上文所介绍的Class结构体中也存在isa指针, 而Class中的isa指针则会指向meta class, 同理, Superclass和Root class的isa指针与Subclass的isa指针类似, 因为元类也是类, 所以元类也存在isa指针, 这是, Subclass和Superclass的isa指针都会指向Root class的meta class, 当然这种循环不能一直下去, 所以Root class的meta class的isa指针会指向自己, 例如所有继承NSObject的类的meta class的isa指针都会指向NSObject的meta class, 而NSObject的meta class的isa指针则会指向自己。值得注意的是, 不管是Subclass还是Superclass, 他们的meta class都是不同的, 即每个类的meta class都不相同。这段可能会比较绕, 天资愚钝, 这张图我也是看了好几十遍才弄懂的, 希望大家能够多想。

下面引用别人写好的一个例子来说明一下,代码中有详细的标注, 我们可以看到打印的结果, 对着结果, 结合上图, 会有更好的理解


屏幕快照 2016-06-09 上午11.24.58.png
屏幕快照 2016-06-09 上午11.30.09.png

类和对象的操作函数
这里我们介绍几个常用的类和对象的操作函数, 如下图:

屏幕快照 2016-06-09 上午11.50.35.png

1.对于class_getName, 传入对应的Class, 则会返回其名称
2.class_getSuperclass(Class cls), 传入对应的Class, 获取其父类
3.class_getInstanceSize(Class cls), 传入对应的Class, 计算出实例的大小
4.class_isMetaClass(Clas cls), 传入对应的Class, 返回的是BOOL类型, 岸段该Class是否为元类
5.class_getInstanceVariable(Class cls, const char *name),获取到指定名称实例成员变量的信息, name为传入的变量名称
6.class_getClassVariable(Class cls, const char *name), 获取类成员变量信息
7.class_copyIvarList(Class cls, unsigned int *outCount), 获取整个成员变量列表
8.objc_getProperty(Class cls, const char *name), 获取指定的属性
9.objc_copyPropertyList(Class cls, unsigned int *outCount), 获取属性列表
注意:这里outCount参数返回的是个数, 在获取玩IvarList和PropertyList以及我们没有提到的MethodList后要使用free()方法来释放。具体的对于类和对象操作的函数我会在代码中给出, 大家可以对照代码, 动手操一番

动态创建类和对象
1.动态创建类


屏幕快照 2016-06-09 下午8.08.41.png

(1).objc_allocateClassPair函数:如果我们要创建一个根类,则superclass指定为Nil。extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
为了创建一个新类,我们需要调用objc_allocateClassPair。然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。
实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。
(2).objc_disposeClassPair函数用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法, 对于我们需要销毁该类或者子类, 我们需要调用object_dispose(id obj)来销毁已经存在的对象, 注意的是object_dispose(id obj)函数需要在MRC环境下调用, 之后在调用objc_disposeClassPair函数销毁已经常见的类。

屏幕快照 2016-06-09 下午8.16.58.png

<1>我们用objc_allocateClassPair动态创建了一个类,继承自MyClass,MyClass是我之前创建好的类
<2>动态添加叫做subMethod1的方法, 而该方法的IMP指针指向函数imp_submethod1的实现
<3>动态添加实例变量, 叫做_ivar1的NSString类型的实例变量
<4>动态添加叫做Property2的属性
<5>调用objc_registerClassPair注册该类, 需要注意的是, 我们在动态添加方法, 属性, 实例变量的时候一定要在objc_allocateClassPair和objc_registerClassPair两个函数调用之间完成。
<6>创建对象, 调用方法
<7>销毁创建的对象, 销毁创建的类

小结:
这篇只是简单的介绍一下runtime对于类和对象的一些简单的函数, 帮助大家了解Objective-C低层的操作, 同时帮助大家对于元类进行理解, 具体的代码会在下面的地址中给出, 大家可以参考代码对于runtime中的函数有着进一步的了解, 下一篇我们会讲述利用runtime创建property和method swizzling, 让大家在平时的编程中能够用上runtime。

代码下载地址:
https://github.com/CveniEs/Runtime.git
https://github.com/CveniEs/Runtime_Example.git

注:本门的很多内容都参考与南峰子的博客, 有兴趣同学可以去学习一下。
参考:
Objective-C Runtime Programming Guide
Objective-C Runtime Reference
南峰子技术博客

推荐阅读更多精彩内容