动态方法决议起因
前面探究了方法的快速查找流程和慢速查找流程,对方法底查找流程有一定的了解。如果快速查找流程和慢速查找流程都没有找打方法的实现,后面的流程是怎么样的,苹果会给一次机会动态方法决议
案例分析
@interface NBPerson : NSObject
-(void)sayHello;
@end
@implementation NBPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NBPerson *nb = [NBPerson alloc];
[nb sayHello];
}
return 0;
}
复制代码
- 控制台经典报错
方法未实现为什么会报 unrecognized selector sent to instance
?
源码探究
- 慢速查找流程的LookUpImpOrForward
- 部分源码显示
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//当消息的快速查找、慢速查找流程都走完还找不到的情况下
//imp会给一个默认值forward_imp,进入消息转发流程
imp = forward_imp;
break;
}
}
return imp;
}
复制代码
- 通过
LookUpImpOrForward
这个方法可以得知,imp
在未找到的时候会被默认赋值一个`forward_imp - forward_imp
只是一个符号,它代表的是
(IMP)_objc_msgForward_impcache
_objc_msgForward_impcache
- 在arm64架构下 此函数的汇编命令只有一个:__objc_msgForward
__objc_msgForward
- 3条指令是指通过函数__objc_forward_handle,获取一个返回值。存放在x17寄存器然后通过TailCallFunctionPointer x17
_objc_forward_handle
- _objc_forward_handle具体实现函数是objc_defaultForwardHandler
- objc_defaultForwardHandler函数中我们可以看到打印在控制台的报错信息
探索流程
- 调用方法
- 汇编快速查找流程
- C++源码慢速查找流程
- lookUpImpOrForward
- 快速查找和慢速查找都找不到的情况下imp会被默认赋值为forward_imp,forward_imp真实代表的是_objc_msgForward_impcache
- _objc_msgForward_impcache具体实现是objc_msgForward
- objc_msgForward拿到_objc_forward_handle返回值存放在x17寄存器,b到[x17]
- _objc_forward_handle -> objc_defaultForwardHandle (这里实现日志)
- 控制台打印日志完成
动态方法决议
- 源码解析
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//当消息的快速查找、慢速查找流程都走完还找不到的情况下
//imp会给一个默认值forward_imp,进入消息转发流程
imp = forward_imp;
break;
}
//找到了sel对应的imp
if (fastpath(imp)) {
goto done;
}
}
/**
* 如果遍历查找的过程找到了,会跳过此步骤,取到done分支,进行后续操作
* 如果找不到,会进行下面这个算法,最终进入resolveMethod_locked函数
* 此算法真正达到的目的为单例,保证一个lookUpImpOrForward
* 只执行一次resolveMethod_locked
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
return imp;
}
复制代码
位运算实现单例解读
操作符号解读:
&
:按位与,是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位都为1时,结果位才为1。^
:按位异或,比较的是二进制位,相同位置数字不同为1,相同为0^=
:按位异或,比较的是二进制位,相同位置数字不同为1,相同为0,将结果赋值为运算符左边。
//初始值
behavior = 3
LOOKUP_RESOLVER = 2
//第一次
behavior & LOOKUP_RESOLVER = 3 & 2 = 0b11 & 0b10 = 0b10 = 2
behavior = behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 0b11 ^ 0b10 = 0b01 = 1
//非第一次
behavior & LOOKUP_RESOLVER = 1 & 2 = 0b01 & 0b10 = 0b00 = 0
//保证了每一个lookUpImpOrForward 函数最多只能执行一次 resolveMethod_locked(动态方法决议),直到behavior 被重新赋值
复制代码
resolveMethod_locked
- 当你调用了一个方法的时候,第一进入
消息的快速查询流程
-> 然后进入消息的慢速查找流程
,依然找不到你实现的地方,此时imp=nil
,理论上来讲程序应该崩溃
- 但是在
开发者
的角度上来讲,此做法会令这个框架不稳定
,或者说这个系统很不友善
。所以此框架决定再给你一次机会
为你提供了一个自定义的imp
返回的机会,resolveMethod_locked此函数就是动态消息转发的入口
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码
- 此函数非常关键的三步
resolveInstanceMethod
:实例方法动态添加imp
resolveClassMethod
:类方法动态添加imp
lookUpImpOrForwardTryCache
,当完成添加之后,回到之前的慢速查找
流程再来一遍
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//系统会在此处为你发送一个消息resolve_sel
//当你的这个类检测了这个消息,并且做了处理
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
//那么此时系统会重新查找,此函数最终会触发LookUpImpOrForwar
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
复制代码
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
复制代码
lookUpImpOrNilTryCache
extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
// LOOKUP_NIL = 4 没有传参数behavior = 0 0 | 4 = 4
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
复制代码
lookUpImpTryCache
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//cls 是否初始化
if (slowpath(!cls->isInitialized())) {
// 没有初始化就去查找 lookUpImpOrForward 查找时可以初始化
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//在缓存中查找sel对应的imp
IMP imp = cache_getImp(cls, sel);
// imp有值 进入done流程
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
//是否有共享缓存
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.`preoptFallbackClass(), sel);
}`
#endif
// 缓存中没有查询到imp 进入慢速查找流程
// behavior = 4 ,4 & 2 = 0 不会进入动态方法决议,所以不会一直循环
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//(behavior & LOOKUP_NIL) = 4 & 4 = 1
//LOOKUP_NIL 只是配合_objc_msgForward_impcache 写入缓存
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
复制代码
判断cls
是否初始化一般都会初始化的
缓存中查找
- 在缓存中查找
sel
对应的imp
- 如果
imp
存在跳转done
流程 - 判断是否有共享缓存给系统底层库用的
- 如果缓存中没有查询到
imp
,进入慢速查找流程
慢速查找流程
- 慢速查找流程中,
behavior
=4
,4
&2
=0
进入动态方法决议,所以不会一直循环 - 最重要的如果没有查询到此时
imp
=forward_imp
,跳转lookUpImpOrForward
中的done_unlock
和done
流程,插入缓存,返回forward_imp
done流程
done
流程: (behavior
&LOOKUP_NIL
) 且imp
=_objc_msgForward_impcache
,如果缓存中的是forward_imp
,就直接返回nil
,否者返回的imp
,LOOKUP_NIL
条件就是来查找是否动态添加了imp
还有就是将imp
插入缓存lookUpImpOrNilTryCache
的主要作用通过LOOKUP_NIL
来控制插入缓存,不管sel
对应的imp
有没有实现,- 如果
imp
返回了有值那么一定是在动态方法决议中动态实现了imp
resolveInstanceMethod 实例探究
@interface NBPerson : NSObject
-(void)sayHello;
@end
@implementation NBPerson
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"--进入%@--",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NBPerson *nb = [NBPerson alloc];
[nb sayHello];
}
return 0;
}
复制代码
//输出结果
2021-08-24 13:56:48.862237+0800 KCObjcBuild[24082:782193] --进入sayHello--
2021-08-24 13:56:48.863491+0800 KCObjcBuild[24082:782193] --进入sayHello--
复制代码
- 从输出日志中我们发现两次查找sayHello方法
第一次我们知道是快速和慢速查找都没查到,然后进入动态方法决议
,那么第二次呢?
- 不妨我们来日志探究一下
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//其他代码省略
printf("方法名=%s--behavior=%d\n",sel,behavior);
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
printf("调用方法决议---%s\n",sel);
return resolveMethod_locked(inst, sel, cls, behavior);
}
//其他代码省略dsa
}
复制代码
- 打印日志
-
从日志中我们看到第一次能调用
behavior=3
,调用之后后面还有个behavior=4
没有调用,然后behavior=2
又调用了,这个behavior=2
从哪里来的呢? -
全局搜索
输出结果
所以这个流程就可以确定是,
快速查找
-> 慢速查找
-> 都没找到就第一次动态方法决议
-> 我们实现reslove_instanceMethod不添加结果就是还没实现
-> class_getInstanceMethod默认behavior = 2 继续慢速查找
-> 第二次动态方法决议 -> 无法找到imp报错
接下来我们动态添加一下方法再看看
//添加实现
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"--进入%@--",NSStringFromSelector(sel));
if (@selector(sayHello) == sel) {
IMP imp = class_getMethodImplementation(self , @selector(sayHello2));
Method meth = class_getInstanceMethod(self , @selector(sayHello2));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(self ,sel, imp, type);;
}
return [super resolveInstanceMethod:sel];
}
- (void)sayHello2{
NSLog(@"--%s---",__func__);
}
复制代码
- 输出日志
- 第一次动态决议都一样,第二次慢速查找的时候因为找到了 sayHello2直接去实现了,没有第二次调用动态方法决议
动态添加方法之后流程
快速查找
-> 慢速查找
-> 都没找到就第一次动态方法决议
-> 我们实现reslove_instanceMethod并动态添加方法
-> class_getInstanceMethod默认behavior = 2 继续慢速查找
-> 查找到sayHello2并实现
继续探究 resolveClassMethod
int main(int argc, const char * argv[]) {
@autoreleasepool {
NBPerson *nb = [NBPerson alloc];
[NBPerson sayNB];
}
return 0;
}
复制代码
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"--进入%@--",NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
复制代码
- 动态添加sayNB2
+(BOOL)resolveClassMethod:(SEL)sel{
if (@selector(sayNB) == sel) {
NSLog(@"resolveClassMethod--进入%@--",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(sayNB2));
Method meth = class_getClassMethod(object_getClass([self class]) , @selector(sayNB2));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(object_getClass([self class]) ,sel, imp, type);;
}
return [super resolveClassMethod:sel];
}
+(void)sayNB2{
NSLog(@"--%s---",__func__);
}
复制代码
- 输出结果和
resolveInstanceMehod
一样,就不展示了
根类探究
- 新建NSObject 分类 NB
#import "NSObject+NB.h"
@implementation NSObject (NB)
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (@selector(sayHello) == sel) {
NSLog(@"resolveInstanceMethod--进入根类%@--",NSStringFromSelector(sel));
}
return NO;
}
@end
复制代码
- NBPerson 和NSObject+NB 都不动态添加方法输出结果
2021-08-24 15:36:25.434378+0800 KCObjcBuild[26342:864477] --进入sayHello--
resolveInstanceMethod--进入根类sayHello--
2021-08-24 15:36:25.435479+0800 KCObjcBuild[26342:864477] --进入sayHello--
resolveInstanceMethod--进入根类sayHello--
复制代码
- NBPerson不动态添加 和NSObject+NB(动态添加)输出结果
2021-08-24 16:15:19.481990+0800 KCObjcBuild[27162:889603] --进入sayHello--
2021-08-24 16:15:19.482336+0800 KCObjcBuild[27162:889603] --进入根类sayHello--
2021-08-24 16:15:19.482417+0800 KCObjcBuild[27162:889603] ---[NSObject(NB) sayHello2]---
复制代码
- NBPerson(动态添加) 和NSObject+NB(动态添加)输出结果
2021-08-24 16:18:09.871925+0800 KCObjcBuild[27226:891858] --进入sayHello--
2021-08-24 16:18:09.872140+0800 KCObjcBuild[27226:891858] ---[NBPerson sayHello2]---
复制代码
- 总结
reslove_instanceMethod动态添加流程
,在类中动态添加,已添加动态添加完成,未添加从父类中添加,以此类推,如果找到根类(NSObject)还未添加 返回NO。 - 再测试类方法结果一致,因为类的isa指向元类,
reslove_classMethod动态添加流程
,在元类中动态添加实例方法,已添加动态添加完成,未添加从元类父类中添加,以此类推,如果找到根元类的父类根类(NSObject)还未添加 返回NO。
AOP
和OOP
的区别
OOP
:实际上是对对象的属性和行为的封装,功能相同的抽取出来单独封装,强依赖性,高耦合AOP
:是处理某个步骤和阶段的,从中进行切面的提取,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,依赖性小,耦合度小,单独把AOP
提取出来的功能移除也不会对主代码造成影响。AOP
更像一个三维的纵轴,平面内的各个类有共同逻辑的通过AOP
串联起来,本身平面内的各个类没有任何的关联
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END