RunTime
1. RunTime简介
因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。
Runtime其实有两个版本:“modern”和 “legacy”。我们现在用的Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。
Runtime基本是用C和汇编(437版本开始较多使用mm文件,但是仍用C语法)实现的,可见苹果为了动态系统的高效而作出了很多努力,你可以在这里下到苹果维护的开源代码runtime源码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。
Objective-C基于RunTime具有相当多的动态特性,基本的也是经常被提到和用到的有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)。
这些动态特性都是在Cocoa程序开发时非常常用的语言特性,而在这之后,OC在底层也提供了相当丰富的运行时的特性,比如枚举类属性方法、获取方法实现等等。
2. RunTime应用场景
a) 基本属性
b) 发送消息
调用方法底层是对象发消息
使用消息机制要#import <objc/message.h>
[receiver message];
// 底层运行时会被编译器转化为:
objc_msgSend(receiver, selector)
// 如果其还有参数比如:
[receiver message:(id)arg...];
// 底层运行时会被编译器转化为:
objc_msgSend(receiver, selector, arg1, arg2, ...)
-[__NSCFNumber lowercaseString]:unrecognized selector
sent to instance 0x87
*** Terminatingapp due to uncaught exception
'NSInvalidArgumentException',reason:
'-[__NSCFNumberlowercaseString]:unrecognized selector sent to instance ox87'
上面这段异常信息是由NSObject的“doesNotRecognizeSelector:”方法所抛出的,此异常表明:消息接收者的类型是__NSCFNumber,而该接受者无法理解名位lowercaseString的选择子。
消息的转发分为两大阶段。第一阶段先征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择子”(unknown selector),这叫做“动态方法解析”(dynamic method resolution)。第二阶段涉及“完整的消息转发机制”。如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法的手段来响 应包含该选择子的消息了。此时,运行期系统会请求接受者以其他手段来处理与消息相关的方法调用。这又细分为两小步。首先,请接受者看看有没有其他对象处理 这条消息。若有,则运行期系统会把消息转给那个对象,于是消息转发过程结束,一起如常。若没有“备援的接收者”,则启动完整的消息转发机制,运行期系统会 把于消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。
1. + (BOOL)resolveClassMethod:(SEL)sel;
2. + (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
3. -(id)forwardingTargetForSelector:(SEL)aSelector;
4. -(void)forwardInvocation:(NSInvocation*)anInvocation;
· 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
· 第二个方法和第一个方法相似,只不过处理的是实例方法。
· 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
· 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
其他常见方法:
#import<objc/runtime.h> : 成员变量、类、方法
Ivar * class_copyIvarList: 获得某个类内部的所有成员变量
Method *class_copyMethodList : 获得某个类内部的所有方法
Method class_getInstanceMethod: 获得某个实例方法(对象方法,减号-开头)
Method class_getClassMethod: 获得某个类方法(加号+开头)
method_exchangeImplementations : 交换2个方法的具体实现
#import<objc/message.h> : 消息机制
objc_msgSend(....)
-class方法返回对象的类;
-isKindOfClass:和 -isMemberOfClass:方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
-respondsToSelector:检查对象能否响应指定的消息;
-conformsToProtocol:检查对象是否实现了指定协议类的方法;
-methodForSelector: 返回指定方法实现的地址。
c) 交换方法
#import "UIViewController+swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (swizzling)
//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (
void)load{
//方法交换应该被保证,在程序中只会执行一次
staticdispatch_once_t
onceToken;
dispatch_once(&onceToken, ^{
//获得viewController的生命周期方法的selector
SEL systemSel =
@selector(viewWillAppear:);
//自己实现的将要被交换的方法的selector
SEL swizzSel =
@selector(swiz_viewWillAppear:);
//两个方法的Method
Method systemMethod = class_getInstanceMethod([
selfclass], systemSel);
Method swizzMethod = class_getInstanceMethod([
selfclass], swizzSel);
//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOLisAdd = class_addMethod(
self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//如果成功,说明类中不存在这个方法的实现
//将被交换方法的实现替换到这个并不存在的实现
class_replaceMethod(
self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}
else{
//否则,交换两个方法的实现
method_exchangeImplementations(systemMethod, swizzMethod);
}
});
}
- (
void)swiz_viewWillAppear:(
BOOL)animated{
//这时候调用自己,看起来像是死循环
//但是其实自己的实现已经被替换了
[
selfswiz_viewWillAppear:animated];
NSLog
(
@"swizzle");
}
@end
d) 动态添加方法
使用场景:一个类方法非常多,一次性加载到内存,比较耗费资源。为什么动态添加方法? OC都是懒加载,有些方法可能很久不会调用比如电商,视频,社交,收费项目:会员机制,要会员才拥有这些功能。
(方法解释详见“发送消息”)
e) 动态添加属性
在category中添加name属性但是并未与当前类进行关联,这样属性保存的地址和关联属性的保存地址是不同的。虽然同样可以完成显式的属性添加,但是当object对象销毁了,object.name并不会随着它的的销毁而销毁。这样显然不是关联关系。既然是添加属性,那么当object销毁了,object.name也应随之销毁。虽然直接在分类中实现setter和getter方法对新添加的属性进行赋值和输出的操作,但是并没有与之产生关联,所以这个时候需要使用runtime进行动态绑定,即将某个属性保存到调用它的对象里。你向哪个对象添加属性,那么就将这个属性保存到哪个对象中。
f) 字典转模型
使用场景:字典转模型时,希望可以不用与字典中属性一一对应
g) 解决是否由于多次连续点击造成重复请求
用运行时和分类,替换UIControl响应事件,根据响应的间隔时间来判断是否执行事件。
h) NSCoding归档和解档