1 引入动态方法决议
iOS方法调用的时候,底层实际是消息发送objc_msgSend
,这就需要找到具体的方法sel
对应的函数指针imp
,查找imp的流程初略可划分为以下几个步骤:
底层会经过快速查找
(详细见:iOS-13.objc_msgSend快速查找流程);
当快速查找未找到对应的imp,将会进入慢速查找
(详细见:iOS-14.objc_msgSend方法慢速查找流程分析);
当慢速查找也未找到对应的imp,将进行动态方法决议
;
当动态方法决议后进行慢速查找(慢速查找后不会再次进入动态方法决议)
,如果仍然没有找imp,则进行消息转发
;
2 方法未实现报错原理
2.1 定义LGPerson类,声明一个类方法和一个对象方法,但均不进行实现
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)say666;
+ (void)sayNB;
@end
NS_ASSUME_NONNULL_END
@implementation LGPerson
@end
2.1 对象方法调用报错
2.2 类方法调用报错
2.3 报错流程
-
方法的调用
到底层解析为消息的发送
,底层根据消息的接受对象和消息方法sel
找到对应的函数指针imp
。 -
函数指针imp的查找经过
快速查找
,慢速查找
,动态方法决议
,快速消息转发
都没有找到对应的imp时,会查找消息接收者中是否实现-methodSignatureForSelector:
方法,并成功返回一个NSMethodSignature对象
,如果未实现,程序在imp查找中返回_objc_msgForward_impcache
-
__objc_msgForward_impcache
具体实现和__objc_msgForward
具体实现
STATIC_ENTRY __objc_msgForward_impcache
//跳转到__objc_msgForward
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
//__objc_forward_handler
//定义在objc-runtime.mm void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
objc_defaultForwardHandler
的具体实现
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
3 动态方法决议resolveMethod_locked
3.1 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 { // 类方法 存储在元类中
resolveClassMethod(inst, sel, cls);//查找类方法
if (!lookUpImpOrNil(inst, sel, cls)) { //重新进入lookUpImpOrNil中查找imp
resolveInstanceMethod(inst, sel, cls);//在底层所有方法都是对象方法,类方法是元类的对象方法,所以仍然需要查找一次对象方法
}
}
// 动态方法决议中可能将其实现指向其他方法,重新走慢速查找流程
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
3.2 resolveMethod_locked执行逻辑
- 在底层,
方法的调用
全部解释为消息发送
- OC接口层存在对象方法和类方法的区分,但底层只有一种消息发送,,所以底层将对象方法和类方法都统一为对象方法,只是存储位置和归属不同,
类的对象方法存储在类结构中
,类的类方法存储在元类中
- 在查找对应的imp是需要
判断cls是否是元类
,分别查找 - cls不是元类,则调用对象方法查找
resolveInstanceMethod
- cls是元类,则调用类方法查找
resolveClassMethod
- cls是元类,调用类方法查找
resolveClassMethod
后,如果未找到imp,仍会调用元类的+resolveInstanceMethod方法查找, - 动态方法决议后,可能将sel的实现指向其他方法,需要
重新走慢速查找流程
3.3 对象方法查找resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 对象方法,查找类中是否实现+ resolveInstanceMethod:方法
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// resolveInstanceMethod not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//调用+resolveInstanceMethod:方法
bool resolved = msg(cls, resolve_sel, sel);
/*
进入动态方法决议需要behavior条件判断
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
*/
//给一次机会,在resolveInstanceMethod方法中添加imp,再进行一次lookUpImpOrNil查找,但此次慢速查找不一定会再次进入动态方法决议
IMP imp = lookUpImpOrNil(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));
}
}
}
3.4 类方法查找resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//类方法 查找类中是否实现 +resolveClassMethod:
if (!lookUpImpOrNil(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;
//调用+resolveClassMethod:
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// 给一次机会,在resolveClassMethod方法中添加imp,再进行一次lookUpImpOrNil查找
IMP imp = lookUpImpOrNil(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));
}
}
}
4 动态方法决议解决方法未实现
4.1 在LGPerson中定义并实现两个方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)say666;
- (void)sayMaster;
+ (void)sayNB;
+ (void)lgClassMethod;
@end
NS_ASSUME_NONNULL_END
@implementation LGPerson
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
NSLog(@"%s",__func__);
}
@end
4.2 调用对象方法报错
LGPerson *person = [LGPerson alloc];
[person say666];
在LGPerson.m中添加方法+ (BOOL)resolveInstanceMethod:(SEL)sel
,在方法中往类的对象方法列表中添加添加imp
,并绑定@selector(say666)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s - %@ 来了",__func__,NSStringFromSelector(sel));
if (sel == @selector(say666)) {
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
//对象方法,存储在类中
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
4.3 调用类方法报错
[LGPerson sayNB];
在LGPerson.m中+ (BOOL)resolveClassMethod:(SEL)sel
,在方法中往元类的对象方法列表中添加imp
,并绑定@selector(sayNB)
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@ 来了",NSStringFromSelector(sel));
if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
//类方法存储在元类中
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
4.4 在NSObjcet中拦截所有方法未实现的动态决议
对象方法动态决议 class的对象方法->【父类的对象方法->父类的父类的对象方法->...->】->NSObject对象方法
类方法动态决议
class的类方法->【父类的类方法->父类的父类的类方法->...->】->NSObject类方法->NSObject对象方法
由此可知,当没有找到方法的的实现时,都会找NSObject的对象方法。所有我们可以NSObject添加一个分类
,实现+resolveInstanceMethod:方法
,拦截所有方法未实现的动态决议,统一处理
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMethod);
return class_addMethod(self, sel, imp, type);
}else if (sel == @selector(sayNB)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
5 两次进行动态方法决议
如下图,如果在resolveInstanceMethod方法中没有实现sel的imp的重新绑定,将继续会方法未实现的错误,而且会调用两次resolveInstanceMethod,也就会有两次进行动态方法决议
在resolveInstanceMethod的消息发送之前断点打印堆栈信息
第一次say666方法调用resolveInstanceMethod
是按照本文上述的逻辑,在快速查找和慢速查找都没有找到imp后进行动态方法决议
第二次say666方法调用resolveInstanceMethod
是在CoreFoundation内部系统调用
,在慢速消息转发methodSignatureForSelector之后
,再次进行了动态方法决议
6 消息转发
动态方法决议之后也没有找到imp将会进行消息转发,开源源码中没有找到相关内容,可以通过以下两种方式探索消息转发的流程:
1) instrumentObjcMessageSends 打印发送消息的日志
2) 通过hopper/IDA反编译
6.1 instrumentObjcMessageSends
通过lookUpImpOrForward 中调用 log_and_fill_cache
,进入logMessageSend
,
logMessageSend源码下方找到instrumentObjcMessageSends
的源码实现,
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
需要打开 objcMsgLogEnabled
开关,才能将消息调用的日志写入
-
在main中调用: 打开 objcMsgLogEnabled 开关 ---
instrumentObjcMessageSends(YES);
-
在main中
通过extern 声明instrumentObjcMessageSends方法
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person sayHello];
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
找到消息发送的日志函数,可知日志存放路径为/tmp/msgSend-%d
,找到/tmp/路径
,查看文件名前缀为msgSend的文件
6.2 通过hopper/IDA反编译
当函数为实现的崩溃堆栈可知程序进入了CoreFoundation中 面对未开源的系统库,可以采用反编译工具分析
我们通过查看image list
找到CoreFoundation的路径
将CoreFoundation可执行文件拖拽到Hopper中,选择x86(64 bits)
全局搜索____forwarding___
找到其定义处,查看伪代码
在___forwarding___会判断forwardingTargetForSelector函数:
是否实现,如果有实现,则进入forwardingTargetForSelector:快速消息转发
,如果未实现则跳转到loc_64a67进入慢速消息转发
6.4 forwardingTargetForSelector:快速消息转发
LGPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)sayHello;
@end
NS_ASSUME_NONNULL_END
LGPerson.m
#import "LGPerson.h"
#import "LGStudent.h"
@implementation LGPerson
// 1: 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// runtime + aSelector + addMethod + imp
// return [super forwardingTargetForSelector:aSelector];
return [LGStudent alloc];
}
@end
LGStudent.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGStudent : NSObject
- (void)sayHello;
@end
NS_ASSUME_NONNULL_END
LGStudent.m
#import "LGStudent.h"
@implementation LGStudent
- (void)sayHello{
NSLog(@"%s",__func__);
}
@end
方法调用
6.5 methodSignatureForSelector:+forwardInvocation:慢速消息转发
跳转到loc_64a67,后会先判断类是否实现methodSignatureForSelector:
方法
如果methodSignatureForSelector:
方法未实现,则跳转至loc_64dd7输出报错
如果methodSignatureForSelector:
方法有实现,则进一步判断forwardInvocation:
是否实现
forwardInvocation:
未实现 则跳转loc_64ec2,后返回nil报unrecognized selector错误
forwardInvocation:
有实现,则调用forwardInvocation:
实现消息的慢速转发
NSInvocation事务类
@interface NSInvocation : NSObject
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
@property (readonly, retain) NSMethodSignature *methodSignature;
- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;
@property (nullable, assign) id target;
@property SEL selector;
- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)invoke;
- (void)invokeWithTarget:(id)target;
@end
7 消息转发注意事项
如果类中未实现methodSignatureForSelector:
,在慢速查找的循环查找imp中可知会遍历父类,并在父类中进行消息转发,最终会找到基类NSObjec
,通过Hopper查看NSObject的methodSignatureForSelector:实现
调用___methodDescriptionForSelector
-->调用class_getInstanceMethod
-->进入OC底层class_getInstanceMethod函数-->lookUpImpOrForword-->再次进行动态方法决议
8 总结
消息发送objc_msgSend
,由sel
找到对应的函数指针imp
,查找imp的流程初略可划分为以下几个步骤:
-
当快速查找未找到对应的imp,将会进入
慢速查找
(详细见:iOS- objc_msgSend方法慢速查找流程分析); -
当慢速查找也未找到对应的imp,将进行
动态方法决议
; -
当动态方法决议仍然没有找imp,则进行
消息转发
; -
消息转发可分为快速的消息转发和慢速的消息转发,先进行
快速的消息转发
,当返回imp=nil进入慢速消息转发