博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS-动态方法决议与消息转发机制
阅读量:4135 次
发布时间:2019-05-25

本文共 5858 字,大约阅读时间需要 19 分钟。

在中我们介绍过,OC是动态语言,对象调用方法其实就是对象接收消息,而消息的发送采用“动态绑定”机制,具体会调用哪个方法直到运行时才确定,然后去执行绑定的代码。

绑定的过程:

以下面代码为例

Person *p = Person.new;[p eat];

方法调用时,编译器会底层将之转换成C语言的函数objc_msgSend(p, @selector(eat));,来对p发送消息。消息接收者p对象通过方法的名称SEL(即eat),首先到该类的方法cache中查找对应的方法实现IMP,如果找到就执行该方法实现;如果没找到就到该类的方法列表(methodLists)中去找,如果找到就执行该方法实现并且缓存到cache中;如果没找到就通过superclass指针到其父类的方法cache和methodLists中去找,如果找到就执行该方法实现并且缓存到cache中;如果在其所有父类的方法cache和methodLists中都没找到对应的IMP,那么程序就会crash!!!

显然crash不是我们想看到了,如何来阻止闪退呢?也就是今天我们要介绍的动态方法决议!

动态方法决议:

还是上面的代码,如果Person类中没有eat方法的实现,会怎么样的?

-[Person eat]: unrecognized selector sent to instance 0x6000012b0460 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person eat]: unrecognized selector sent to instance 0x600002fa0400'

造成 crash 的原因:对象无法处理 eat 对应的 selector,也就是没有找到相应的方法实现。

Object-C为我们提供一种名为动态方法决议的手段,使得我们可以在运行时动态地为一个 selector 提供实现。我们只要实现 +resolveInstanceMethod: 和/或 +resolveClassMethod: 方法,并在其中为指定的 selector 提供实现即可(通过调用运行时函数 class_addMethod 来添加)。这两个方法都是 NSObject 中的类方法:

// 类方法进行决议+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // 对象方法进行决议+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

sel表示要决议的方法名,返回值文档中说是表示动态决议成功与否。如果在该函数内为指定的 selector 提供实现,无论返回 YES 还是 NO,编译运行都是正确的;但如果在该函数内并不真正为 selector 提供实现,无论返回 YES 还是 NO,运行都会 crash,道理很简单,selector 并没有对应的实现。

注意:只有当编译器没有找到 selector 对应的方法实现的时候才会进入动态方法决议,如果决议之前找到对应实现就不会进入动态方法决议。

// 当调用未实现的对象方法,回调该动态方法决议+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolve instance method --- %@",NSStringFromSelector(sel)); if (sel == @selector(eat)) {
// 方式一:调用下面 C 函数 //class_addMethod(self.class, sel, (IMP)dynamicResolveMethod, "v@:@"); //方式二 :调用下面 OC 函数 class_getMethodImplementation 改变现有的实现 IMP imp = class_getMethodImplementation(self.class, @selector(dynamicResolveMethod)); class_addMethod(self.class, sel, imp, "v@:"); return YES; } return [super resolveInstanceMethod:sel];}
// 实现 C 函数void dynamicResolveMethod(id self, SEL _cmd) {
NSLog(@"C 函数添加成功");}// 实现 OC 方法- (void)dynamicResolveMethod{
NSLog(@"OC 对象方法添加成功");}

方法一:Objective C 中的方法其实就是至少带有两个参数(self 和 _cmd)的普通 C 函数,因此在上面的代码中提供这样一个 C 函数 dynamicResolveMethod,让它来充当对象方法 eat 这个 selector 的动态实现。

方法二:selector动态绑定已经存在的对象方法的IMP,可以使用class_getMethodImplementation()函数:

OBJC_EXPORT IMP _Nullableclass_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)     OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

cls是Person类,SEL是Person类中已经存在的方法实现对应的方法名,返回值是函数指针IMP,然后把该IMP动态的绑定到eat的selector。这个办法有个好处就是当我们测试动态方法决议也没有找到eat 的动态实现的时候,不会编译报错。如果使用(IMP)dynamicResolveMethod的形式,而且没有dynamicResolveMethod函数,即没有该方法实现,会编译报错。

如果我们把上面的方法实现代码注释掉,即在动态方法决议的时候仍然不给eat提供相应的IMP,效果会怎么样?

-[Person eat]: unrecognized selector sent to instance 0x600000ae48a0

运行起来后,程序crash,原因和之前一样,没有找到eat的对应的方法实现,怎么来再挽救这次闪退呢?runtime又为我提供一种挽救的方式:消息转发。

消息转发:

消息转发就是在编译器没有找到eat对应的方法实现的时候,会把我们的消息转发出去,具体转发给谁,需要我们自己设定,来看下消息转发的方法:

- (id)forwardingTargetForSelector:(SEL)aSelector{
Dog *dog = [[Dog alloc] init]; if ([dog respondsToSelector:aSelector]) {
return dog; } return [super forwardingTargetForSelector:aSelector];}
@implementation Dog- (void)eat{
NSLogo(@"eat");}@end

代码解释:消息转发方法- (id)forwardingTargetForSelector:(SEL)aSelector,该方法会在动态放决议也解决不了的时候回调(系统自动回调),在该方法中来实现消息转发的功能,参数aSelector为eat的selector,返回值为id类型,表示转发的目标对象(备用的消息处理者:dog对象)。上述代码中,我们创建一个Dog类,在Dog类的实现代码中,添加一个eat方法实现,目的是为了动态绑定eat的selector相应的方法实现。

再运行程序,程序没有异常。如果我们Dog类中的没有eat的selector对应的IMP又会怎么样呢?结果会crash,原因依然是:

-[Person eat]: unrecognized selector sent to instance 0x600000ae48a0

这时候runtime还会为我们提供最后的防线来防止程序异常,启用完整的消息转发机制

// 如果签名 不为nil ,那么runtime会创建一个 NSInvocation 对象,并发送forwardInvocation:消息- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
//查找父类签名 NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector]; if (methodSignature == nil) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return methodSignature;}// invocation 调用- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector; Person *person = Person.new; if ([person respondsToSelector:sel]) {
[anInvocation invokeWithTarget:person]; } else {
NSLog(@"真的找不到 %@ 的方法实现!!!",NSStringFromSelector(sel)); }}// 当方法签名为nil,调用此方法。程序crash- (void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"程序crash了");}

代码解释:系统向对象发送methodSignatureForSelector:消息,传入eat方法的selector,返回一个方法签名,如果方法签名为nil,则会调用doesNotRecognizeSelector:方法,程序crash;如果方法签名不为空,系统会创建一个 NSInvocation 对象,并发送forwardInvocation:消息。对象anInvocation中保存着方法的信息(方法签名methodSignature、方法名SEL、消息接收者target等),判断Person类中是否有该selector相应的方法实现,如果有就调用 NSInvocation 的invokeWithTarget:方法,如果没有就说明真的找不到eat方法的方法实现了,runtime只能帮到这里,虽然没有得到响应,但是程序进程是保住了,没有出现crash。总结如下图:

在这里插入图片描述
总结

通过上面介我们知道动态方法决议是先于消息转发的,如果向一个对象发送它无法处理的消息(selector),那么Runtime会按照如下次序进行处理:首先看是否为该 selector 提供了动态方法决议机制,如果提供了则进行动态方法决议,且真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程;如果没有提供动态方法决议就看是否为该 selector 提供了消息转发机制,如果提供了则进行消息转发,此时,无论消息转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示。);如果没提供消息转发机制,则转到消息重定向,处理消息;如果方法签名为空则程序 crash;

上面介绍的是对象方法的动态方法决议以及消息转发机制,在Objective-C中,,类不能使用前面为实例显示的声明语法定义属性,但它们可以接收消息。如果是类方法如何?基本上一样,有些许差别,类方法的动态方法决议会调用:

+ (BOOL)resolveClassMethod:(SEL)sel{
}

class_addMethod()中要传入Class类型参数:

// 注意:类方法都在元类中,实例方法在类中        Class class = objc_getMetaClass(NSStringFromClass(self.class).UTF8String);        IMP imp = class_getMethodImplementation(class, @selector(classMethodOtherName:));        class_addMethod(class, sel, imp, "v@:");

消息转发和消息重定位也是类方法:

+ (id)forwardingTargetForSelector:(SEL)aSelector{
}+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
}+ (void)forwardInvocation:(NSInvocation *)anInvocation{
}+ (void)doesNotRecognizeSelector:(SEL)aSelector{
}

转载地址:http://tgivi.baihongyu.com/

你可能感兴趣的文章
第四章 微信飞机大战
查看>>
九度:题目1008:最短路径问题
查看>>
九度Online Judge
查看>>
九度:题目1027:欧拉回路
查看>>
九度:题目1012:畅通工程
查看>>
九度:题目1017:还是畅通工程
查看>>
九度:题目1034:寻找大富翁
查看>>
第五章 图着色问题
查看>>
第六章 背包问题——01背包
查看>>
第七章 背包问题——完全背包
查看>>
51nod 分类
查看>>
1174 . 区间中最大的数
查看>>
1136 . 欧拉函数
查看>>
面试题:强制类型转换
查看>>
Decorator模式
查看>>
Template模式
查看>>
State模式
查看>>
Observer模式
查看>>
Iterator模式
查看>>
淘宝分类导航条;纯css实现固定导航栏
查看>>