objc消息轉發機制探索

回顧一下二分查找

sel在method_排序是遞增排列的,eg:01,02,03,04,05,06,07,08。markdown

動態方法決議

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);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
複製代碼

代碼舉例:app

建立一個分類重寫resolveInstanceMethod方法oop

+ (BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"%@ 來了",NSStringFromSelector(sel));
    if (sel == @selector(say666)) {
        NSLog(@"%@ 來了",NSStringFromSelector(sel));

        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    else if (sel == @selector(sayNB)) {

        IMP imp           = class_getMethodImplementation(objc_getMetaClass("CFPerson"), @selector(cfClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("CFPerson"), @selector(cfClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("CFPerson"), sel, imp, type);
    }
    return NO;
}
複製代碼

logMessageSend內部實現

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))
    {
        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;
}
複製代碼

/temp/msgSends:運行時寫入的目錄路徑ui

instrumentObjcMessageSends 

該方法是監控objc底層消息發送spa

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

    // Shortcut NOP
    if (objcMsgLogEnabled == enable) //objcMsgLogEnabled:控制開關
        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);

    objcMsgLogEnabled = enable;
}
複製代碼

建立一個CFPerson類,並添加一個方法sayHellocode

直接運行會在調用方法處崩潰orm

此刻咱們嘗試一下用instrumentObjcMessageSends方法將其包裹一下對象

在.m文件裏添加實現方法排序

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

    // runtime + aSelector + addMethod + imp
    return [super forwardingTargetForSelector:aSelector];
}
複製代碼

forwardingTargetForSelector來自蘋果官方解釋:文檔

當一個對象發送一個消息沒有接收者的時候,則返回其第一接收者。

此時再建立一個CFStudnet接收類供接收

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

    return [CFStudnet alloc];}
複製代碼

對沒有進行轉發處理的消息將會還調用methodSignatureForSelector方法,具體解釋見蘋果官方文檔

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

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    // GM  sayHello - anInvocation - "拋出去" - anInvocation
    anInvocation.target = [CFStudnet alloc];
    // anInvocation 保存 - 方法
    [anInvocation invoke];
}
複製代碼

小結: objc轉發流程

相關文章
相關標籤/搜索