转自:https://zhuanlan.zhihu.com/p/27248527
1 个月前
Objective-C 扩展了 C 语言,并加入了面向对象特性,这个扩展的核心是一个用 C 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。每次 Apple 公司升级 Runtime 库, 所有使用 Objective-C开发的app 都收益,不得不佩服这里的设计哲学。我们可以自定义的类跟Objective-C Runtime 库交互,通过 Runtime 特性,实现一些特殊的需求。
objc/runtime.h
主要有 objc_method,objc_ivar, objc_category, objc_property, objc_class 数据结构。
我们需要了解 Runtime 的目标:
1. 了解这些数据结构;
2. 这些数据结构对应操作方法(API使用),源码链接:SwiftZimu/RuntimeLearning;
3. 优秀的开源库Mantle库是怎么使用 runtime 特性的。参考另一篇文章《 Mantle 源码分析》;
4. Objective-C 消息转发原理。参考另一篇文章《Objective-C 消息转发机制详解》;
完成上面4个步骤,算是对 Objective-C runtime 有些了解了。
1. runtime 中的数据结构分析
在 objc/objc.h 上定义了:
SEL是 objc_selector 结构指针, 可以理解为区别方法的 ID,IMP是函数指针。
id是 objc_object 结构指针,是实例对象(instance object),在 objc_object 里有一个 isa 成员,是 Class 类型,表示它的类是谁。Class 是 objc_class 结构指针,类对象(class object), 定义在第一张图里的,它也有 isa 成员,表示它的元类是谁。
super_class: 指向该类的父类,如果是根类(root class, NSObject或者 NSProxy),那么 super_class 为 nil.
【图片来自网络】
所有的 metaClass isa 都指向 meta Root Class, 而 meta Root Class 的 isa 指向自己,并且从 Root Class (NSObject, NSProxy)继承过来。
1.1 objc_method
method_name 是 SEL类型的方法名 , objc_selector 结构指针, objc_selector 定义:What is the objc_selector implementation?
method_types 存储方法的参数类型和返回值类型。详细参考下图 Type Encoding。
method_imp 方法的具体实现, 函数指针。
1.2 objc_method_list
method_count 方法个数
space 占用空间
method_list 可变长度的 objc_method 结构数组
1.3 objc_ivar
ivar_name 变量名
ivar_type 变量类型 Type Encoding.(参考上面的图)
ivar_offset 基地址偏移字节
1.4 objc_ivar_list
ivar_count 变量个数
space 占用空间
ivar_list 可变长度的 objc_ivar 结构数组
1.5 objc_category
category_name 类别的名称
class_name 给哪个类增加类别
instance_methods 类别的实例方法
class_methods 类别的类方法
protocols 类别实现的协议列表
objc_protocol_list 是一个链表,count 表示多少个 可变长的Protocol数组, Protocol 也是 objc_object 结构。
1.6 objc_property_attribute_t
1.7 objc_cache
mask: 指定分配cache buckets的总数。在方法查找中,Runtime使用这个字段确定数组的索引位置。
occupied 实际占用cache buckets的总数。
buckets 指定Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长;
objc_msgSend 每调用一次方法后,就会把该方法缓存到cache列表中,下次的时候,就直接优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法。
2. Runtime 中的数据结构操作方法(API 使用)
本节主要内容:【内容比较多,按照成员变量、方法、属性、协议这种顺序来组织, 每小节后面都有一个例子 】
2.1 获取 super class,name, version, instance size
2.2 获取 ivarList, methodList, propertyList, protocolList
2.3 添加 ivar, method, property, protocol
2.4 添加 class
2.5 ivar 相关
2.6 method 相关
2.7 Selector 相关
2.8 IMP 相关
2.9 property 相关
2.10 protocol 相关
2.11 Associative References 相关
2.1 获取 super class,name , version, instance size
2.1.1 父类 super class 【 getSuperclass 】
2.1.2 类名 name 【 getName 】
2.1.3 类的版本信息 version 【 getVersion 】
2.1.4 类对象的大小 instance size 【 getInstanceSize 】
2.1.5 Example
2.2 获取 ivarList, methodList, propertyList, protocolList
2.2.1 成员变量列表 ivar list 【 copyIvarList 】
输出了 RLAccount 所有的成员变量,其中红色圈中的是私有变量,name,age 是属性,内部自动添加了_name, _age 成员变量。 ivar_getName 使用方法参考 2.5 Ivar 相关 内容。
2.2.2 方法列表 method list 【 copyMethodList 】
上图输出了 age, name 属性的 get, set 方法, 除此之外,Objective-C 对 RLAccount 类,自动增加了.cxx_destruct, 通过名字可以猜出是析构方法,用于释放资源。不包含父类 (NSObject)中的方法。
2.2.3 属性列表 property list 【 copyPropertyList 】
打印了自己的属性, name和 age,不打印父类的属性。property_getName 参考 2. 9 小节 property 相关。
2.2.4 采用的协议列表 protocol list 【 copyProtocolList 】
打印了自己实现的协议,不包括父类的协议。protocol_getName 参考 2.10 小节 protocol 相关
2.3 添加 ivar, method, property, protocol
2.3.1 添加成员变量 【 add Ivar 】
在现有的 RLAccount 类上,增加一个 _email 成员变量,结果增加失败,看接口说明是,不支持在现有的类上增加成员变量。只能在 objc_allocateClassPair 新建的类上添加,参考 2.4 小节 添加 class。
2.3.2 添加方法 【 add Method 】
添加了一个 newMethodOperation 方法,该方法的实现,由 IMP 指定,通过 一个 block指定。参考 2.8 小节 IMP 相关。
2.3.3 添加属性 【 add Property 】
添加了一个名字为 newPropertyName 的属性, 它的类型 为 NSString(T), readOnly(R) 的 。objc_property_attribute_t 更多方法参考 2.9 小节 property 相关。
2.3.4 添加协议 【 add Protocol 】
对 RLAccount 类实现一个 Swift4Indexable 协议。更多协议内容,请参考 2.10小节 protocol 相关。
2.4 添加 class
2.4.1 创建一个新类 alloc class pair
2.4.2 注册类 register class pair
2.4.3 释放创建的类 dispose class pair
2.4.4 Example:
知乎专栏添加了一个 UserInfo类,设置了一个 email成员变量。使用 KVO 设置值,值为本人的邮箱(swiftlang@163.com, 欢迎联系进行技术交流)。注册了 printLoginInfo 方法,可以同过设置的 imp 中的 block 进行回调,也可以发送消息的方式。最后释放实例对象和类对象。
sel_registerName 参考 2.7 小节 SEL 相关。消息转发的相关内容,参考另一篇文章《Objective-C 消息转发机制详解》
2.5 ivar 相关
2.5.1 ivar name 【 变量名 】
2.5.2 ivar type encoding 【 变量类型 】
2.6 method 相关
2.6.1 method name 【 方法名 】
2.6.2 method type encoding 【 方法参数类型及返回类型 】
2.6.3 获取 method implementation
2.6.4 设置 method implementation
2.6.5 交换 method implementation
2.6.6 替换 method
2.6.7 Example
下图是开源库 Aspect 上的,每个步骤我都加上注释,对应着上面几个方法的使用:
2.7 Selector 相关
2.7.1 获取 SEL 名字
2.7.2 判断 SEL 是否相等
2.7.3 将 SEL注册到 runtime
2.8 IMP 相关
2.8.1 通过一个 block 获取 IMP
2.8.2 通过一个 IMP 获取 block
2.8.3 删除 IMP
上面使用了 block构建 IMP, 并且使用结束后将 IMP 里的 block 删掉。
2.9 property 相关
2.9.1 get Name
2.9.2 get attributes
2.9.3 copy attribute list
2.9.4 copy attribute value
2.9.5 Example
打印了RLAccount 的属性的 attribute,name 为 T, &, N, V, R等等, T 为类型,V对应于值为,编译器自动生成的变量,eg: _age, _name。
苹果文档上的定义:
2.10 protocol 相关
2.10.1 get protocol name 【 获取协议的名字 】
2.10.2 get protocol 【 通过名字获取协议结构 】
2.10.3 判断两个协议是否相等
2.10.4 一个协议是否采用另一个协议
2.10.5 获取 runtime 上所有协议结构
2.11 associated reference 相关
2.11.1 set associated object 【 设置关联对象 】
2.11.2 association policy 【 设置关联对象的属性:assign, retain, copy 】
2.11.3 get associated object 【 获取关联对象 】
2.11.4 remove associated objects 【 删除对象中的所有关联对象 】
2.11. 5 Example:
下图是开源库Aspect 库上的代码:
以 aliasSelector 作为 Key, 获取NSObject 对象的关联对象,如果没有,则新建一个 AspectsContainer 对象,并且设置为关联对象,使用 OBJC_ASSOCIATION_RETAIN, 原子强引用标识。
下图是开源库 Mantle上的代码。获取键值集合,如果没有设置,则遍历类对象以及其父类的属性列表,如果没有加入到keys 中,则添加进来。最后设置keys 为关联对象。在 《Mantle 源码分析》上详细讲解相关内容。
源码链接:SwiftZimu/RuntimeLearning