美文网首页
iOS底层系列14 -- 消息流程的动态方法决议与转发

iOS底层系列14 -- 消息流程的动态方法决议与转发

作者: YanZi_33 | 来源:发表于2021-03-01 20:05 被阅读0次

iOS底层系列12 -- 消息流程的快速查找iOS底层系列13 -- 消息流程的慢速查找这两篇文章中分别介绍了objc_msgSend快速查找与慢查找;如果都没有找到方法实现就会进入动态方法决议和消息转发。

  • 动态方法决议:慢速查找流程未找到方法实现时,会执行一次动态方法决议;
  • 消息转发:如果动态方法决议仍然没有找到方法实现时,则进行消息转发。

如果动态方法决议消息转发都没有做任何操作,就会报我们日常开发中常见的方法未实现的崩溃报错,即unrecognized selector sent to instance xxxx

动态方法决议

在慢速查找中没有找到方法实现,会尝试进行一次动态方法决议,源码实现如下:

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 (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
  • 判断当前类如果不是元类,执行实例方法的动态方法决议resolveInstanceMethod
  • 当前类是元类,执行类方法的动态方法决议resolveClassMethod,如果在元类中没有找到或者为空,则在元类的实例方法的动态方法决议resolveInstanceMethod中查找,主要是因为类方法在元类中是实例方法,所以还需要查找元类中实例方法的动态方法决议

实例方法的动态方法决议resolveInstanceMethod源代码实现如下:

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, 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(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));
        }
    }
}
  • 在发送resolveInstanceMethod消息前,首先查找cls类中是否有该方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找resolveInstanceMethod方法
    • 如果没有实现,则直接返回
    • 如果有实现,则发送resolveInstanceMethod消息
  • 再次慢速查找实例方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找实例方法

实例方法的动态方法决议代码测试:

@interface YYPerson : NSObject

- (void)walk;

+ (void)speak;

@end
#import "YYPerson.h"
#import <objc/runtime.h>

@implementation YYPerson

- (void)walk_resolve{
    NSLog(@"walk_resolve");
}

//给当前类动态添加一个方法和方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) isEqualToString:@"walk"]) {
        NSLog(@"walk -- ");
        IMP imp = class_getMethodImplementation(self, @selector(walk_resolve));
        Method method = class_getInstanceMethod(self,@selector(walk_resolve));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self,sel,imp,type);
    }
    return [super resolveInstanceMethod:sel];
}
@end

在resolveInstanceMethod方法内部打下断点,当应用停在断点,控制台输入bt命令,打印出函数调用堆栈如下所示:

Snip20210301_112.png

类方法的动态方法决议代码测试:

#import "YYPerson.h"
#import <objc/runtime.h>

@implementation YYPerson

+ (void)speak_resolve{
    NSLog(@"speak_resolve");
}

+ (BOOL)resolveClassMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) isEqualToString:@"speak"]) {
        IMP imp = class_getMethodImplementation(objc_getMetaClass("YYPerson"), @selector(speak_resolve));
        Method method  = class_getInstanceMethod(objc_getMetaClass("YYPerson"), @selector(speak_resolve));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("YYPerson"),sel,imp,type);
    }
    return [super resolveClassMethod:sel];
}

@end

断点同上设置,函数调用堆栈如下:

Snip20210301_113.png

若动态方法决议没有手动取实现,就会进入消息转发的流程。

消息转发

消息转发的处理主要分为两个部分:

  • 快速转发:当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到forwardingTargetForSelector方法;

1> 如果返回消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程;
2> 如果返回nil,则进入慢速消息转发.

  • 慢速转发:执行到methodSignatureForSelector方法;

1> 如果返回的方法签名为nil,则直接崩溃报错;
2> 如果返回的方法签名不为nil,走到forwardInvocation方法中,对invocation事务进行处理,如果不处理也会造成崩溃报错.

消息快速转发的代码测试如下:

#import <Foundation/Foundation.h>

@interface YYStudent : NSObject

- (void)walk;

@end
#import "YYStudent.h"

@implementation YYStudent

- (void)walk{
    NSLog(@"%s",__func__);
}

@end
#import "YYPerson.h"
#import <objc/runtime.h>
#import "YYStudent.h"

@implementation YYPerson

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == @selector(walk)) {
        return [YYStudent new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end
  • YYPerson类没有实现walk实例方法,其调用forwardingTargetForSelector函数即消息的快速转发,将消息转发给实现了walk方法的YYStudent实例对象。

forwardingTargetForSelector函数内部断点调试如下:

Snip20210302_114.png
  • 可以看出消息的快速转发调用了CoreFoundation框架。

若当消息的快速转发没有进行处理,就会进入消息的慢速转发流程,测试代码如下:

#import "YYPerson.h"
#import <objc/runtime.h>
#import "YYStudent.h"

@implementation YYPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(walk)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    SEL sel = anInvocation.selector;
    YYStudent *student = [[YYStudent alloc]init];
    if ([student respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:student];
    }else{
        [anInvocation doesNotRecognizeSelector:sel];
    }
}

@end
  • methodSignatureForSelector为需要慢速转发的消息提供方法签名;
  • forwardInvocation系统会为需要转发的消息创建一个NSInvocation事务对象,我们可以对NSInvocation事务进行处理,如果不处理也不会崩溃报错。

总结

综合 iOS底层系列12 -- 消息流程的快速查找iOS底层系列13 -- 消息流程的慢速查找以及本篇,objc_msgSend发送消息的整体流程就分析完成了,现作出如下总结:

  • 快速查找流程:首先在类的缓存cache中查找指定方法的实现;
  • 慢速查找流程:如果缓存中没有找到,则在类的方法列表中查找(二分法),如果还是没找到,则根据类/元类的继承链在父类的缓存和方法列表中查找;一直递归到nil;
  • 动态方法决议:如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod 方法;
  • 消息转发:如果动态方法决议没有处理,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发
  • 如果消息转发也没有处理,则程序直接报错崩溃unrecognized selector sent to instance

super的本质

  • 定义类YYPerson,实现一个run方法,
  • 定义一个子类YYStudent,继承自YYPerson;
    先上代码:
#import "YYStudent.h"

@implementation YYStudent

- (instancetype)init{
    self = [super init];
    if (self) {
        NSLog(@"[self class] = %@",[self class]);
        NSLog(@"[self superclass] = %@",[self superclass]);
        
        NSLog(@"[super class] = %@",[super class]);
        NSLog(@"[super superclass] = %@",[super superclass]);
    }
    return self;
}

- (void)run{
    [super run];
    NSLog(@"%s",__func__);
}
@end
  • 终端输入xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc YYStudent.m,看到YYStudent类的run方法的C++底层实现如下:
static void _I_YYStudent_run(YYStudent * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("YYStudent"))}, sel_registerName("run"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_YYStudent_1b9e06_mi_4,__func__);
}
  • 看到[super run],转成了objc_msgSendSuper(struct objc_super,selector)
  • 其中第一个参数是一个objc_super类型的结构体,内部有两个参数分别为:selfself的父类,也就是YYStudent的实例对象与YYPerson类;
  • [super message],底层转成objc_msgsendSuper({self,父类对象},@selector(message));消息的接受者依然是当前实例对象,只不过消息的查找越过了当前类,直接去其父类YYPerson中去查找;

测试代码:

#import <Foundation/Foundation.h>
#import "YYStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YYStudent *student = [[YYStudent alloc]init];
    }
    return 0;
}
  • 控制台打印结果:
Snip20210630_37.png
  • 看到 [self class]与[super class]打印结果相同;
  • 首先[self class] --> objc_msgSend(self,@selector(class))
  • 然后[super class] --> objc_msgSendSuper({self,YYPerson},@selector(class)
  • class方法实现是在NSObject基类里面的,其实现如下:
- (Class)class{
     object_getClass(self);
}
  • 也就是说class方法返回的结果,取决于消息的接受者self;
  • 所以 [self class]与[super class] 消息的接受者都是self,当前的YYStudent类的实例对象,所以最终的调用结果是相同的,都返回YYStudent类;
  • superClass的方法实现是在NSObject基类里面,其实现如下:
- (Class) superClass{
     object_get SuperClass(object_getClass(self));
}
  • 所以[self superClass]与[super superClass] 返回的都是YYPerson类;

objc_msgsend(instance,@selector)底层实现

底层实现逻辑如下图所示:

objc_msgSend.png
  • 主要分为三个阶段:
  • 第一个阶段:方法的查找,又分为缓存查找,class结构体内部的class_rw_t中的方法列表查找;
    • 缓存查找:是汇编语言实现的;
    • class结构体内部的class_rw_t中的方法列表查找:是C语言实现的;
  • 第二个阶段:消息的动态方法解析;
  • 第三个阶段:消息的转发。

相关文章

网友评论

      本文标题:iOS底层系列14 -- 消息流程的动态方法决议与转发

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