什么是runtime
runtime是属于OC的底层,可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)。 在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者。
例如:
// 调用无参数的方法:
[receiver selector];
// 运行时会被编译器转化为:
objc_msgSend(receiver, selector)
// 调用有参数的方法:
[receiver selector:(id)arg...];
// 运行时会被编译器转化为:
objc_msgSend(receiver, selector, arg, ...)
runtime的作用
- 动态创建一个类(比如KVO的底层实现)
- 动态地为某个类添加属性/方法。可以用于封装框架(扩展、修改) 这就是我们runtime机制的主要运用方向
- 遍历一个类中所有的成员变量(属性)/所有方法。(比如字典–>模型:利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上;还有归档和解档,利用runtime遍历模型对象的所有属性)
- 利用runtime实现使用Block回调的KVO
- 利用runtime实现多播委托
runtime相关文件与函数
#import
#import
官方文档翻译有些地方不是很通顺,可以直接查看官方文档
Objective-C Runtime这里有runtime文件中方法的中文注释,不甚清晰,有空再自己整理一份。
runtime的使用
有三种方式可以使用Runtime:
- Objective-C 源代码
- NSObject 方法
- Runtime 方法
Objective-C 源代码
通常情况下我们写的Objective-C代码,编译时会自动生成包含runtime特性的数据结构(包含了class、category、protocol中定义的信息)和方法。
NSObject类中定义的方法
Cocoa中大部分的类都是继承自NSObejct(NSProxy类例外,是一个抽象类)。
一些情况下,NSObject 类仅仅定义了完成某件事情的模板,并没有提供所需要的代码。例如 -description
方法,该方法返回类内容的字符串,该方法主要用来调试程序。NSObject 类并不知道子类的内容,所以它只是返回类的名字和对象的地址,NSObject 的子类可以重新实现。
还有一些 NSObject 的方法可以从 Runtime 系统中获取信息,允许对象进行自我检查。例如:
- class方法返回对象的类;
- isKindOfClass: 和 -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
- respondsToSelector: 检查对象能否响应指定的消息;
- conformsToProtocol:检查对象是否实现了指定协议类的方法;
- methodForSelector: 返回指定方法实现的地址。
使用runtime库函数
常用的接口如下:
/// 获取类
Class PersonClass = object_getClass([Person class]);
/// SEL是selector在 Objc 中的表示:
SEL oriSEL = @selector(test1);
/// 获取类方法
Method oriMethod = Method class_getClassMethod(Class cls , SEL name);
/// 获取实例方法
Method class_getInstanceMethod(Class cls , SEL name)
/// 添加方法
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
/// 替换原方法实现
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
/// 交换两个方法
method_exchangeImplementations(oriMethod, cusMethod);
/// 获取一个类的属性列表(返回值是一个数组)
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
/// 获取一个类的方法列表(返回值是一个数组)
Method *methodList = class_copyMethodList([self class], &count);
/// 获取一个类的成员变量列表(返回值是一个数组)
Ivar *ivarList = class_copyIvarList([self class], &count);
/// 获取成员变量的名字
const char *ivar_getName(Ivar v);
/// 获取成员变量的类型
const char *ivar_getTypeEndcoding(Ivar v);
/// 获取一个类的协议列表(返回值是一个数组)
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
/**
* set方法
* 将值value 跟对象object 关联起来(将值value 存储到对象object 中)
* 参数 object:给哪个对象设置属性
* 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
* 参数 value:给属性设置的值
* 参数policy:存储策略 (assign 、copy 、 retain就是strong)
**/
void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy);
/// 利用参数key 将对象object中存储的对应值取出来
id objc_getAssociatedObject(id object , const void *key);
runtime相关术语
Method
Method 代表了可以独立完成一个功能的函数,其结构包括方法名、方法类型 以及方法实现:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
SEL
SEL是方法选择器(Swift中是Selector类),其实就是runtime中发送message时带的方法名(怀疑是一个string,类似className + method
的组合,这也是OC为什么不能进行函数重载的原因)。
OC中,performSelector
系列方法所传的selector
都是SEL的一个实例。
IMP
IMP是指向一个方法实现的函数指针,定义如下:
typedef id (*IMP)(id, SEL, ...);
id
id 是一个参数类型,它是指向某个类的实例的指针。
Class
Class 其实是指向 objc_class 结构体的指针。定义如下:
typedef struct objc_class *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;
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
} OBJC2_UNAVAILABLE;
从 objc_class 可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。
其中 objc_ivar_list 和 objc_method_list分别是成员变量列表和方法列表。
Ivar
Ivar 是表示成员变量的类型。包含了对象名称、对象类型,以及对象地址(基地址偏移字节)。
Cache
Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。
Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。
参考文章:
runtime从入门到精通(一)—— 初识runtime
runtime从入门到精通(二)—— 官方文档翻译
runtime从入门到精通(三)—— runtime常用接口方法
runtime从入门到精通(四)—— 一些runtime相关术语的数据结构
iOS Runtime 详解
iOS开发-Runtime详解
OC运行时Runtime探究