IOS底层探索动态方法决议

_objc_msgForward_impcache处理

上篇文章分析了慢速查找流程,如果递归完父类任然没有找到imp,就将imp = forward_imp,因为

const IMP forward_imp = (IMP)_objc_msgForward_impcache;
复制代码

objc源码全局搜索_objc_msgForward_impcache

image.png
汇编中重点看一下__objc_forward_handler的实现。

全局搜索_objc_forward_handler(注意去掉一个下划线),来到c++代码:

image.png
最终(消息转发流程)找不到方法实现,报错并打印报错信息。
找不到方法最后报错流程:
_objc_msgForward_impcache->_objc_forward_handler->_objc_fatal

在慢速查找过程中,如果没有查找到imp,并且没有执行过动态方法决议就执行一次动态方法决议

动态方法决议源码分析

在慢速查找过程lookUpImpOrForward中,执行了一次动态方法决议:

   //第一次:0011 & 0010 = 0010 (满足条件,只有一次机会)
   //第二次:0000 & 0010 = 0000 (不满足条件)
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //0010 ^ 0010 = 0000 = behavior
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
复制代码

resolveMethod_locked定义:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 程序员 你是不是傻 没有这个方法 - imp-nil
    // 奔溃 - 友善
    // 给你一次机会 拯救地球 -> imp
    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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    
    // chances are that calling the resolver have populated the cache
    // so attempt using it
   // lookUpImpOrForwardTryCache->_lookUpImpTryCache->_lookUpImpTryCache->lookUpImpOrForward
    //再次慢速查找一次
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码

对象方法决议源码分析

对象方法决议resolveInstanceMethod实现

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    //如果cls中实现了resolveInstanceMethod
    SEL resolve_sel = @selector(resolveInstanceMethod:);
     ///如果cls中没有实现resolveInstanceMethod,系统默认也会有一个实现,所以下面不会return
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    //系统会给cls中resolveInstanceMethod发送消息
    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 = lookUpImpOrNilTryCache(inst, sel, cls);

   省略部分代码
}
复制代码

对象方法决议:如果cls中实现了resolveInstanceMethod,系统会给resolveInstanceMethod发送消息,如果没有实现,系统也会有个resolveInstanceMethod默认实现。
image.png

对象方法决议案例调试

1.准备调试代码:

#import <Foundation/Foundation.h>

@interface ABPerson : NSObject
-(void)doSomething;
@end

@implementation ABPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        ABPerson *p = [ABPerson new];
        [p doSomething];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
复制代码

2.执行结果必然报错:
image.png
3.按照对象方法决议源码分析,修改ABPerson实现代码

#import <objc/message.h>
@interface ABPerson : NSObject
-(void)doSomething;
@end

@implementation ABPerson
-(void)saySomething
{
    NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
    if (sel == @selector(doSomething)) {
        IMP sayImp     = class_getMethodImplementation(self, @selector(saySomething));
        Method method    = class_getInstanceMethod(self, @selector(saySomething));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, sayImp, type);
    }
    return [super resolveInstanceMethod:sel];
}
复制代码

4.执行结果不再报错,并且成功将调用doSomething转为调用saySomething

image.png

类方法决议源码分析

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
   //如果没有实现resolveClassMethod,return,不过系统默认实现了一个,这里不会return
    if (!lookUpImpOrNilTryCache(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);
        }
    }
     //系统会给nonmeta(元类)中resolveClassMethod发送消息
     //nonmeta(元类)中的方法是以对象方法的形式存在的
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls);
省略部分代码
}

复制代码

类方法决议:系统会给nonmeta(元类)resolveClassMethod发送消息,如果没有实现,会给元类的父类(元类的父类的父类直到NSObject为止)
发消息。系统也会有个resolveClassMethod默认实现。
image.png

类方法决议案例调试(一)

1.准备调试代码:

#import <Foundation/Foundation.h>
#import <objc/message.h>

@interface ABPeople : NSObject
+ (void)doSomething;
@end

@implementation ABPeople

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        [ABPeople doSomething];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
复制代码

2.执行结果必然报错:

image.png

3.按照对象方法决议源码分析,修改ABPeople实现代码

#import <objc/message.h>

@interface ABPeople : NSObject
+ (void)doSomething;
@end

@implementation ABPeople
+ (void)saySomething
{
    NSLog(@"%s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@"resolveClassMethod--------- :%@-%@",self,NSStringFromSelector(sel));
    if (sel == @selector(doSomething)) {
        IMP sayImp     = class_getMethodImplementation(objc_getMetaClass("ABPeople"), @selector(saySomething));
        Method method    = class_getInstanceMethod(objc_getMetaClass("ABPeople"), @selector(saySomething));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("ABPeople"), sel, sayImp, type);
    }

    return [super resolveClassMethod:sel];
}

@end
复制代码

4.执行结果不再报错,并且成功将调用doSomething转为调用saySomething
image.png

类方法决议案例调试(二)

我们知道了类方法是在元类中是以实力方法的方式存在的,元类的父类一直往上找是会找到NSObject的,那么NSObject分类也是能够实现类方法决议的,类方法决议代码修改:

#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ABPeople : NSObject
//实力方法
- (void)doSomething;
//类方法
+ (void)doSomething;
@end

@implementation ABPeople

@end
@interface NSObject (Cate)

@end
@implementation NSObject (Cate)
-(void)saySomething
{
    NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
    if (sel == @selector(doSomething)) {
        IMP sayImp     = class_getMethodImplementation(self, @selector(saySomething));
        Method method    = class_getInstanceMethod(self, @selector(saySomething));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, sayImp, type);
    }
    return NO;
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        [ABPeople doSomething];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
复制代码

image.png
仍然可以成功运行。类别方式的决议同样适用于实例方法

AOP面向切面编程

通过NSObject类别的方式能够全局监听方法找不到,通过 if (sel == @selector(doSomething)) ,可以将特定方法,如doSomething进行后续的处理,如上传日志等。
这样做就能够对原来的代码无侵入,动态的注入代码。但是这样做也会带来相应的性能消耗,如过多的if (sel == @selector(doSomething)) 判断,同时截断了后面的消息转发流程。
所以不建议在动态方法决议阶段做AOP

instrumentObjcMessageSends写入函数调用日志

instrumentObjcMessageSends源码分析

instrumentObjcMessageSends函数定义:

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    // 如果成立,获得一些方法跟踪
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);
   //flag赋值给objcMsgLogEnabled
    objcMsgLogEnabled = enable;
}

复制代码

objcMsgLogEnabledlogMessageSend函数中的使用:

bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char	buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
       //写下日志,路径为:"/tmp/msgSends-%d", (int) getpid ()
        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;
}
复制代码

instrumentObjcMessageSends使用

instrumentObjcMessageSends使用很简单,只需要三步:

1.导出函数供外部使用:

extern void instrumentObjcMessageSends(BOOL flag);
复制代码

2.在需要跟踪的函数上下加上instrumentObjcMessageSends,如:

ABPeople *p = [ABPeople new];
instrumentObjcMessageSends(YES);
[p doSomething];
instrumentObjcMessageSends(NO);
复制代码

3.文件夹前往路径:/tmp/msgSends

image.png

image.png

总结

  • 无论是对象方法还是类方法,在方法找不到的时候都有一次动态方法决议的机会,需要在类中实现resolveInstanceMethod(对象方法实现),resolveClassMethod(类方法实现),通过此方法,可以进行后续的处理,如果没有实现将进入消息转发流程。
  • 类方法也是按实例方法的形式存在元类中,按照isa继承链,方法找不到,会一直找到NSObjct,通过NSObjct类别实现对象动态方法决议,可以进行全局监听,进行AOP,但不建议在这个阶段做AOP
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享