iOS 开发 深入浅出Runtime运行时之方法动态处理(Dynamic Method Resolution)详解

    xiaoxiao2021-12-14  45

    前言

    在查看本篇博客之前请查看 iOS 开发 深入浅出Rumtime运行时之消息发送机制详解

    Object-C中向一个对象发送它无法处理的消息,会出现什么情况?

    我们知道发送消息是通过 objc_send(id, SEL, …) 来实现的,它会首 先在对象的类对象的 cache,method list 以及父类对象的 cache, method list 中依次查找 SEL 对应 的 IMP;如果没有找到且实现了方法动态处理机制就会进行方法动态处理,如果没有实现方法动态处理机制或处理失败且实现了消息转发机制就会进入消息转发流程,否则程序 crash。也就是说如果同时供了方法动态处理和消息转发,那么方法动态处理先于消息转发,只有当方法动态处理依然无法正确方法 selector 的 实现,才会尝试进行消息转发。在前文中,我并没有详细讲解方法动态处理,因此本文将详细介绍之。

    动态方法处理

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

    + (BOOL)resolveClassMethod:(SEL)name; + (BOOL)resolveInstanceMethod:(SEL)name; 参数 name 是需要被动态方法处理的 selector;返回值文档中说是表示动态决议成功与否。不涉及消息转发的情况下,如果在该函数内为指定的 selector 供实现,无论返回 YES 还是 NO, 编译运行都是正确的;但如果在该函数内并不真正为 selector 供实现,无论返回 YES 还是 NO,运 行都会 crash,道理很简单,selector 并没有对应的实现,而又没有实现消息转发。 resolveInstanceMethod 是为对象方法进行决议,而 resolveClassMethod 是为类方法进行决议。

    下面我们用动态方法决议手段来修改上面的代码:

    // // Persion.m // Persion // // Created by zhouyu on 2016/12/1. // Copyright © 2016年 demo. All rights reserved. // #import "Persion.h" #include <objc/runtime.h> // 运行时的动态方法处理在动态运行时添加一个dynamicMethodIMP方法去实现run方法 void dynamicMethodIMP(id self, SEL _cmd) { NSLog(@" >> dynamicMethodIMP called---经过动态方法处理,run方法能正常执行了: 跑步更健康"); } @implementation Persion // 注释掉run方法的实现 不用动态方法处理和消息转发机制处理会崩溃 //- (void)run { // NSLog(@"跑步更健康"); //} /* 关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。 */ // 对象方法,给该对象再一次实现run方法的机会--name未实现的方法 +(BOOL)resolveInstanceMethod:(SEL)name { NSLog(@" >> Instance resolving (实例对象动态处理未实现的):%@方法", NSStringFromSelector(name)); // 判断是否是指定的未实现的run方法 if (name == @selector(run)) { class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:name]; } +(BOOL)resolveClassMethod:(SEL)name { NSLog(@" >> Class resolving %@", NSStringFromSelector(name)); return [super resolveClassMethod:name]; } @end

    Objective C 中的方法其实就是至少带有两个 参数(self 和 _cmd)的普通 C 函数,因此在上面的代码中 供这样一个 C 函数 dynamicMethodIMP, 让它来充当对象方法 run 这个 selector 的动态实现。因为 run 是被对象所调用, 所以它被认为是一个对象方法,因而应该在 resolveInstanceMethod 方法中为其 供实现。通过调用

    class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");

    就能在运行期动态地为 name 这个 selector 添加实现:dynamicMethodIMP。class_addMethod 是 运行时函数,所以需要导入头文件:objc/runtime.h。

    dynamicMethodIMP 被调用了,crash 没有了!万事大吉!

    下面我把 resolveInstanceMethod 方法中为 selector 添加实现的那一行屏蔽了,消息转发就应该会进 行:

    /* 关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。 */ //class_addMethod([self class], name, (IMP)dynamicMethod IMP, "v@:");

    在这里,resolveInstanceMethod 使诈了,它声称成功(返回 YES )决议了 selector,但是并没有真正 供实现,被编译器发觉而 示相应的错误信息。那它的返回值到底有什么作用呢,在它没有 供真正的 实现,并且 供了消息转发机制的情况下,YES 表示不进行后续的消息转发,返回 NO 则表示要进行后 续的消息转发。

    源码剖析

    让我们来看看运行时系统是如何进行动态方法决议的,下面的代码来自苹果官方公开的源 码 objc-class.m.

    /*********************************************************************** * _class_resolveMethod * Call +resolveClassMethod or +resolveInstanceMethod and return * the method added or NULL. * Assumes the method doesn't exist already. **********************************************************************/ PRIVATE_EXTERN Method _class_resolveMethod(Class cls, SEL sel) { // 初始化方法 Method meth = NULL; // 判断当前类是否是元类 if (_class_isMetaClass(cls)) { // 如果是元类,就从原来的方法列表中找到方法名sel meth = _class_resolveClassMethod(cls, sel); } // 元类中找不到该方名,就从实力对象所在的类寻找方法名 if (!meth) { meth = _class_resolveInstanceMethod(cls, sel); } // 找到了之后就通过这个类在运行时动态地添加这个方法的实现 if (PrintResolving && meth) { _objc_inform("RESOLVE: method %c[%s %s] dynamically resolved to %p", class_isMetaClass(cls) ? '+' : '-', class_getName(cls), sel_getName(sel), method_getImplementation(meth)); } // 将实现的方法的返回 return meth; }

    类方法决议与实例方法决议大体相似,在这里就只看实例方法决议部分了:

    /*********************************************************************** * _class_resolveInstanceMethod * Call +resolveInstanceMethod and return the method added or NULL. * cls should be a non-meta class. * Assumes the method doesn't exist already. **********************************************************************/ static Method _class_resolveInstanceMethod(Class cls, SEL sel) { BOOL resolved; Method meth = NULL; // 是否实现了 resolveInstanceMethod,如果没有返回 NULL if (!look_up_method(((id)cls)->isa, SEL_resolveInstanceMethod, YES /*cache*/, NO /*resolver*/)) { return NULL; } // 调用 resolveInstanceMethod,并获取返回值 resolved = ((BOOL(*)(id, SEL, SEL))objc_msgSend)((id)cls, SEL_resolveInstanceMethod, sel); if (resolved) { // 返回值为 YES,表示 resolveInstanceMethod 声称它已经成功添加实现,则再次查找 method list // +resolveClassMethod adds to self meth = look_up_method(cls, sel, YES/*cache*/, NO/*resolver*/); if (!meth) { // resolveInstanceMethod 使诈了,它声称成功添加实现了,但实际没有,给出警告信息,并返回 NULL // Method resolver didn't add anything? _objc_inform("+[%s resolveInstanceMethod:%s] returned YES, but " "no new implementation of %c[%s %s] was found", class_getName(cls), sel_getName(sel), class_isMetaClass(cls) ? '+' : '-', class_getName(cls), sel_getName(sel)); return NULL; } } // 其他情况下返回 NULL return meth; }

    动态方法处理–总结

    1,首先判断是否实现了 resolveInstanceMethod,如果没有实现,返回 NULL,进入下一步处理;

    2,如果实现了,调用 resolveInstanceMethod,获取返回值;

    3,如果返回值为 YES,表示 resolveInstanceMethod声称它已经 供了 selector 的实现,因此再次 查找 method list,如果依然找到对应的 IMP,则返回该实现,否则示警告信息,返回 NULL,进入下 一步处理;

    4,如果返回值为 NO,返回 NULL,进入下一步处理(消息转发机制);

    转载请注明原文地址: https://ju.6miu.com/read-970610.html

    最新回复(0)