美文网首页
Runtime — 动态方法决议

Runtime — 动态方法决议

作者: Dezi | 来源:发表于2020-06-16 17:49 被阅读0次

前言

消息发送 中,当查不到方法时,会进行动态方法决议,下面我们就来具体分析一下动态方法决议过程中,系统如果操作。

一、动态方法的实现

  • 如果是元类,则是类方法,调用 _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_resolveClassMethodSEL_resolveInstanceMethod 消息。
    注意:根类NSObject已经实现了这个方法,默认返回 NO,因此继承 NSObject 的类不会走该方法。
  • 调用解析器方法 ( SEL_resolveInstanceMethod ) 完成后 , 检查有没有当前 selimp,对应输出成功或失败的日志。

由此我们可以知道崩溃原因是当前传递的方法找不到实现地址,如果要防止崩溃,我们需要给崩溃方法指定一个存在的 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]

四、总结

  1. 当调用方法进行消息发送时,却找不到方法 imp,程序会崩溃。
  2. 但是消息发送期间,系统会给一次容错机会,也就是动态方法决议(区分对象方法和类方法).
  3. 那为了避免崩溃,我们可以重写+ (BOOL)resolveInstanceMethod:(SEL)sel+ (BOOL)resolveClassMethod:(SEL)sel方法,并给一个可实现的方法imp
  4. 注意:当然可以在动态方法决议中避免因找不到方法而崩溃,但是代码冗余严重,此处并不建议。

但是在动态方法决议找不到方法时,后边其实还有一步,也就是消息转发机制。下篇继续探索 消息转发机制

相关文章

网友评论

      本文标题:Runtime — 动态方法决议

      本文链接:https://www.haomeiwen.com/subject/ihgqohtx.html