小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
当调用实例对象未实现的方法时,会报出一个经典错误unrecognized selector sent to instance
,在消息处理机制中,当系统找不到方法,最先进入方法动态决议的流程:
- 系统提供给开发者的挽救机会
- 判断当前
cls
是否为元类 - 如果不是元类,调用类对象的
resolveInstanceMethod
方法 - 否则,是元类,调用类对象的
resolveClassMethod
方法 - 如果未能解决,调用类对象所属元类的
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))) {
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
- 入参:
inst
:实例对象sel
:找不到的实例对象cls
:类对象
- 调用
lookUpImpOrNilTryCache
函数,内部调用_lookUpImpTryCache
函数,对当前类对象的resolveInstanceMethod
方法进行消息慢速查找- 在NSObject中,该方法已默认实现
- 继承自NSObject的类对象,不会被return拦截
- 系统使用
objc_msgSend
,发送resolveInstanceMethod
消息- 消息接收者为类对象
- 消息主体中的
SEL
为resolveInstanceMethod
- 参数为找不到的实例方法
- 调用
lookUpImpOrNilTryCache
函数,对之前找不到的实例方法进行消息慢速查找- 如果在
resolveInstanceMethod
成功处理,返回处理后的imp - 如果依然找不到方法,返回
_objc_msgForward_impcache
函数地址,进入消息转发流程 案例
在LGPerson.h中,声明sayNB实例方法
- 如果在
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
-(void)sayNB;
@end
在LGPerson.m中,实现say666
实例方法和resolveInstanceMethod
类方法,未实现sayNB
实例方法
#import "LGPerson.h"
#import <objc/runtime.h>
@implementation LGPerson
-(void)say666{
NSLog(@"实例方法-say666");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel==@selector(sayNB)){
NSLog(@"resolveInstanceMethod:%@,%@", self, NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(say666));
Method methodSay666 = class_getInstanceMethod(self, @selector(say666));
const char *type = method_getTypeEncoding(methodSay666);
return class_addMethod(self, @selector(sayNB), imp, type);
}
return [super resolveInstanceMethod:sel];
}
@end
- 如果调用的实例方法为
sayNB
,动态添加sayNB
方法,并将imp
填充为say666
的函数地址 在main函数中,调用实例对象per的sayNB
方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *per= [LGPerson alloc];
[per sayNB];
}
return 0;
}
-------------------------
//输出结果:
实例方法-say666
- 自动进入
resolveInstanceMethod
方法