IOS底層探索動態方法決議

_objc_msgForward_impcache處理

上篇文章分析了慢速查找流程,若是遞歸完父類任然沒有找到imp,就將imp = forward_imp,由於c++

const IMP forward_imp = (IMP)_objc_msgForward_impcache;
複製代碼

objc源碼全局搜索_objc_msgForward_impcache程序員

image.png 彙編中重點看一下__objc_forward_handler的實現。編程

全局搜索_objc_forward_handler(注意去掉一個下劃線),來到c++代碼:markdown

image.png 最終(消息轉發流程)找不到方法實現,報錯並打印報錯信息。 找不到方法最後報錯流程: _objc_msgForward_impcache->_objc_forward_handler->_objc_fatal函數

在慢速查找過程當中,若是沒有查找到imp,而且沒有執行過動態方法決議就執行一次動態方法決議源碼分析

動態方法決議源碼分析

在慢速查找過程lookUpImpOrForward中,執行了一次動態方法決議:post

//第一次: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實現ui

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.pngspa

對象方法決議案例調試

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轉爲調用saySomethingimage.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
相關文章
相關標籤/搜索