前言
在 消息发送 中,当查不到方法时,会进行动态方法决议,下面我们就来具体分析一下动态方法决议过程中,系统如果操作。
一、动态方法的实现
- 如果是元类,则是类方法,调用
_class_resolveClassMethod
,这里如果imp
为空时,会调用一次对象方法_class_resolveInstanceMethod
,这是因为isa
走位,根源类的父类是NSObject
。- 如果不是元类,说明方法为实例方法,调用
_class_resolveInstanceMethod
。- 类方法和实例方法的区别是:类方法存在元类中,实例方法存在类对象中,都是存储在方法列表中。因此调用类方法时,在根元类中没用找到方法时,会走到父类
NSObject
中通过实例方法查找一次。
因此当调用类方法时 , 继承链走到根源类并没有查找到时 , 是需要继续走其父类 NSObject 查一次动态方法解析器的 , 而 NSObject 作为类对象 , 是需要通过实例方法去查找的 .
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst); // 已经处理
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 对象方法 决议
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
二、实例方法和类方法决议
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 系统给你一次机会 - 你要不要针对 sel 来操作一下下
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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));
}
}
}
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_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 = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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));
}
}
}
SEL SEL_resolveInstanceMethod = NULL;
SEL SEL_resolveClassMethod = NULL;
+ (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_resolveClassMethod
或SEL_resolveInstanceMethod
消息。
注意:根类NSObject
已经实现了这个方法,默认返回NO
,因此继承NSObject
的类不会走该方法。- 调用解析器方法 ( SEL_resolveInstanceMethod ) 完成后 , 检查有没有当前
sel
的imp
,对应输出成功或失败的日志。
由此我们可以知道崩溃原因是当前传递的方法找不到实现地址,如果要防止崩溃,我们需要给崩溃方法指定一个存在的 imp
,保证这次方法的正常调用。
三、方法防崩溃实验
1. 方法调用
两个方法只有声明,没有实现。
// 实例方法
DZStudent *student = [[DZStudent alloc] init];
[student saySomething];
// 类方法
[DZStudent sayLoveYou];
2. 动态决议方法
#import "DZStudent.h"
#import <objc/message.h>
@implementation DZStudent
- (void)sayHello{
NSLog(@"%s",__func__);
}
+ (void)receiveObjc{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"对象方法来了: %s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(saySomething)) {
NSLog(@"说saySomething");
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
// 类方法查找顺序类 -> 元类 -> NSObject, 如果找不到走动态方法决议
// 1: resolveClassMethod 你是否处理 - 只要注意元类
// 2: resolveClassMethod 没有处理 - resolveInstanceMethod
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"类方法来了: %s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(sayLoveYou)) {
NSLog(@"说sayLoveYou");
IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("DZStudent"), @selector(receiveObjc));
Method sayHMethod = class_getClassMethod(objc_getMetaClass("DZStudent"), @selector(receiveObjc));
const char *sayHType = method_getTypeEncoding(sayHMethod);
// 类方法在元类,所以需要获取元类objc_getMetaClass("DZStudent")
return class_addMethod(objc_getMetaClass("DZStudent"), sel, sayHIMP, sayHType);
}
return [super resolveClassMethod:sel];
}
@end
// 打印
如果动态方法决议不做处理,则会crash并报错如下:
2020-06-16 17:29:29.522658+0800 DZTest[32526:4408299] -[DZStudent saySomething]: unrecognized selector sent to instance 0x101d53c00
2020-06-16 17:28:30.843578+0800 DZTest[32389:4405848] +[DZStudent sayLoveYou]: unrecognized selector sent to class 0x1000023d8
动态方法决议做处理后,方法调用成功:
2020-06-16 16:20:27.315164+0800 DZTest[25206:4282828] 对象方法来了: +[DZStudent resolveInstanceMethod:] - saySomething
2020-06-16 16:20:27.315871+0800 DZTest[25206:4282828] 说saySomething
2020-06-16 16:20:27.316067+0800 DZTest[25206:4282828] -[DZStudent sayHello]
2020-06-16 16:20:27.316335+0800 DZTest[25206:4282828] 类方法来了: +[DZStudent resolveClassMethod:] - sayLoveYou
2020-06-16 16:20:27.316446+0800 DZTest[25206:4282828] 说sayLoveYou
2020-06-16 16:20:27.316554+0800 DZTest[25206:4282828] +[DZStudent receiveObjc]
四、总结
- 当调用方法进行消息发送时,却找不到方法
imp
,程序会崩溃。 - 但是消息发送期间,系统会给一次容错机会,也就是动态方法决议(区分对象方法和类方法).
- 那为了避免崩溃,我们可以重写
+ (BOOL)resolveInstanceMethod:(SEL)sel
和+ (BOOL)resolveClassMethod:(SEL)sel
方法,并给一个可实现的方法imp
。 - 注意:当然可以在动态方法决议中避免因找不到方法而崩溃,但是代码冗余严重,此处并不建议。
但是在动态方法决议找不到方法时,后边其实还有一步,也就是消息转发机制。下篇继续探索 消息转发机制。
网友评论