1. Runtimes概念
OC 是一门动态语言,他不仅需要编译器,还需要运行时系统进行代码编译.Runtimes 就执行了这个运行时系统代码编译工作.
Runtime就是运行时,是Apple用C和汇编语言编写的一套C语言的API,它正是 Objective-C这门动态语言的核心。计算机唯一能识别的语言是机器语言,高级编程语言需要先编译为汇编语言,再由汇编语言编译为机器语言才能被计算机识别。而 Objective-C语言不能被直接编译为汇编语言,它必须先编译为C语言,然后再编译为汇编语言。而Runtime正是编译器将我们写的 Objective-C代码编译为C语言时用到的核心库。
C语言,静态语言,在编译阶段就要决定调用哪些函数属性,如果函数未实现就会编译报错。
OC语言,动态语言,在编译阶段并不能决定真正调用哪些函数,只要函数声明过即使没有实现也不会报错。
2. 消息机制的基本原理
OC 中 方法调用都是类似 [receiver selector]; 的形式,其本质就是让对象在运行时发送消息的过程。
[receiver selector]; 在编译阶段与运行阶段分别作了什么
编译阶段:[receiver selector]; 方法被编译器转换为:
objc_msgSend(receiver,selector) (不带参数)
objc_msgSend(recevier,selector,org1,org2,…)(带参数)
运行时阶段:消息接受者 recevier 寻找对应的 selector。
通过 recevier 的 isa 指针 找到 recevier 的 Class(类);
在 Class(类) 的 cache(方法缓存) 的散列表中寻找对应的 IMP(方法实现);
如果在 cache(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list(方法列表) 中找对应的 selector,如果找到,填充到 cache(方法缓存) 中,并返回 selector;
如果在 Class(类) 中没有找到这个 selector,就继续在它的 superClass(父类)中寻找;
一旦找到对应的 selector,直接执行 recevier 对应 selector 方法实现的 IMP(方法实现)。
若找不到对应的 selector,消息被转发或者临时向 recevier 添加这个 selector 对应的实现方法,否则就会发生崩溃。
在上述过程中涉及了好几个新的概念:objc_msgSend、isa 指针、Class(类)、IMP(方法实现) 等,下面我们来具体讲解一下各个概念的含义。
3. Runtime 用来做什么
3.1 基本作用
1.工程基本功能调用
2.在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法;
3.遍历一个类中所有的成员变量、属性、以及所有方法
4.消息传递、转发
3.1 项目中的典型用法
1.给系统分类添加属性、方法 objc_setAssociatedObject()
2.方法交换 (Method Swizzling)
3.获取对象的属性、私有属性
4.字典转换模型 yymodel
5.KVC、KVO
6.归档(编码、解码)
7.NSClassFromString class<->字符串
8.消息转发
4. Runtime 相关概念
1.isa Class对象,指向objc_class结构体的指针,也就是这个Class的MetaClass(元类)
- 类的实例对象的 isa 指向该类;该类的 isa 指向该类的 MetaClass
- MetaCalss的isa对象指向RootMetaCalss
- 如果MetaClass是RootMetaCalss,那么该MetaClass的isa指针指向它自己
1.super_class Class对象指向父类对象 - 如果该类的对象已经是RootClass,那么这个super_class指向nil
- MetaCalss的SuperClass指向父类的MetaCalss
- MetaCalss是RootMetaCalss,那么该MetaClass的SuperClass指向该对象的RootClass
经典示意图
ivars 类中所有属性的列表,使用场景:我们在字典转换成模型的时候需要用到这个列表找到属性的名称,去取字典中的值,KVC赋值,或者直接Runtime赋值
methodLists 类中所有的方法的列表,类中所有方法的列表,使用场景:如在程序中写好方法,通过外部获取到方法名称字符串,然后通过这个字符串得到方法,从而达到外部控制App已知方法。
cache 主要用于缓存常用方法列表,每个类中有很多方法,我平时不用的方法也会在里面,每次运行一个方法,都要去methodLists遍历得到方法,如果类的方法不多还行,但是基本的类中都会有很多方法,这样势必会影响程序的运行效率,所以cache在这里就会被用上,当我们使用这个类的方法时先判断cache是否为空,为空从methodLists找到调用,并保存到cache,不为空先从cache中找方法,如果找不到在去methodLists,这样提高了程序方法的运行效率
protocols 故名思义,这个类中都遵守了哪些协议,使用场景:判断类是否遵守了某个协议上
5.Runtime消息机制
- 消息发送
- 找到方法 -->调用方法 -->方法回调
- 首先消息发动给对象,遍历对象结构体的methodLists,找到对象响应的方法实现,如果没有就向上查找其父类的方法列表,一单找到就会调用方法,将接受对象的指针传给他
- cache 在进行列表查询时总是很消耗时间的,所以添加了方法cache的概念 将刚刚使用的方法,常用的方法加入cache中,提高方法寻找速度.
6.Runtime消息转发
在使用过程中当5中的方法不能再方法列表中找到的时候,就会报错,抛出异常,
提示找不到方法实现
解决方案
1.实现方法,需要写代码主动实现代码
2.runtime转发,找不到方法来实现,那我们就换发方法来实现吧
方法找不到实现方法时会调用该函数,
+(BOOL)resolveInstanceMethod:(SEL)sel 实例方法解析
+(BOOL)resolveClassMethod:(SEL)sel 类方法解析消息转发第一次方法解析中没有处理方法将会调用
-(id)forwardingTargetForSelector:(SEL)aSelector
将未知SEL作为参数传入,寻找另外对象处理,如果可以处理,返回该对象当我们在前面两个步骤都没有处理该未知SEL时,就会到第三个步骤
-(void)doesNotRecognizeSelector:(SEL)aSelector如果第三步也没能实现
(void)doesNotRecognizeSelector:(SEL)aSelector
调用崩溃-
流程图
7.Runtime消息机制使用
1.分类添加属性
核心代码 手动实现get 与set方法
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY);
} - (NSString *)name {
return objc_getAssociatedObject(self, &nameKey);
}
2.Method Swizzling 方法交换
这是runtime的灵魂所在 , B哥一点 这可以当做简单的代码HOOK
甲方
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
减方法
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
方法交换
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
原有被交换方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
要交换的分类新方法
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));