#方法决议和方法转发

1.方法决议

上文 方法慢速查找流程 中对lookUpImpOrForward方法进行分析,当前sel寻找imp的过程中,当查找父类到NSObject或者查到了类继承上限的时候,imp被赋值forward_imp,开始进行方法决议,这篇文章主要分析这个流程。

方法决议定义

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

如果当前类不是元类,调用resolveInstanceMethod,否则调用resolveClassMethod再通过lookUpImpOrNil判断调用resolveInstanceMethod,最后再通过lookUpImpOrForward寻找一遍。
具体流程图如下:

resolveMethod_locked 流程分析.png

疑问:元类方法 调用resolveClassMethod 之后为什么又调用了resolveInstanceMethod?
因为 类方法储存在元类中,元类方法又是元类实例方法,所以需要元类对resolveInstanceMethod进行决议。

2.消息转发

2.1 通过本地文件打印查看转发流程

在研究cache_insert的方法的时候,有过一个log_and_fill_cache方法

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill(cls, sel, imp, receiver);
}

里面的objcMsgLogEnabled控制本地方法打印

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

// 1: objcMsgLogEnabled 控制开关
// 2: extern

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

    objcMsgLogEnabled = enable;
}

mian.m中定义

extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        instrumentObjcMessageSends(YES);
        [person sayInstanceMethod];  //空方法没有实现
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

运行后查看/tmp/msgSends路径文件

方法转发方法调用

从文件中得知调用resolveInstanceMethod方法后对forwardingTargetForSelectormethodSignatureForSelector方法进行了调用。
LGPerson.m中增加方法

@implementation LGPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"%s ---->sel = %s",__func__, sel);
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"%s ---->sel = %s",__func__, aSelector);
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s ---->sel = %s",__func__, aSelector);
    return [super methodSignatureForSelector:aSelector];
}
@end
控制台打印

查看堆栈

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff336876e6 CoreFoundation`CFStringGetLength + 6
    frame #1: 0x00007fff336b1816 CoreFoundation`CFStringAppend + 229
    frame #2: 0x00007fff336f1e4b CoreFoundation`-[NSArray componentsJoinedByString:] + 309
    frame #3: 0x00007fff337f795f CoreFoundation`__handleUncaughtException + 761
    frame #4: 0x00007fff6c5fa5a3 libobjc.A.dylib`_objc_terminate() + 90
    frame #5: 0x00007fff6aacd887 libc++abi.dylib`std::__terminate(void (*)()) + 8
    frame #6: 0x00007fff6aad01a2 libc++abi.dylib`__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 27
    frame #7: 0x00007fff6aad0169 libc++abi.dylib`__cxa_throw + 113
    frame #8: 0x00007fff6c5f86ed libobjc.A.dylib`objc_exception_throw + 350
    frame #9: 0x00007fff337fdbe7 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    frame #10: 0x00007fff336e33bb CoreFoundation`___forwarding___ + 1427
    frame #11: 0x00007fff336e2d98 CoreFoundation`__forwarding_prep_0___ + 120
  * frame #12: 0x0000000100003b40 002-instrumentObjcMessageSends辅助分析`main(argc=1, argv=0x00007ffeefbff370) at main.m:18:9
    frame #13: 0x00007fff6d7a0cc9 libdyld.dylib`start + 1

___forwarding___之后调用了-[NSObject(NSObject) doesNotRecognizeSelector:]方法,程序出现了报错,并没有显示forwardingTargetForSelector方法调用,找下__forwarding_prep_0______forwarding___方法的调用,通过image list命令在控制台找出CoreFoundation库的位置,用ida软件打开。

__forwarding_prep_0___.png

__forwarding_prep_0___方法中调用了___forwarding___,继续看。

void *__fastcall ___forwarding___(__int64 a1, __int64 a2)
{
  __int64 v3; // rax
  void *(**v4)(void *, const char *, ...); // r12
  void *v5; // rbx
  const char *v6; // r13
  unsigned int v7; // eax
  __int64 v8; // r12
  void *v9; // rax
  unsigned int v10; // ecx
  const __CFString *v11; // rsi
  void *v12; // rax
  void *v13; // r12
  __int64 *v14; // r13
  __int64 v15; // rax
  void *v16; // r15
  void *v17; // rsp
  void *v18; // rsp
  char v19; // r14
  __int64 v20; // rax
  __int64 v21; // rax
  char *v22; // rax
  char *v23; // rbx
  char v24; // al
  void *v25; // rax
  __int64 v26; // r14
  int v27; // er9
  __int64 v29; // rbx
  __int64 v30; // r14
  __int64 v31; // rax
  __int64 v32; // [rsp+0h] [rbp-160h]
  __int64 v33; // [rsp+8h] [rbp-158h]
  __int64 *v34; // [rsp+10h] [rbp-150h]
  const char *v35; // [rsp+18h] [rbp-148h]
  void *v36; // [rsp+20h] [rbp-140h]
  void *(**v37)(void *, const char *, ...); // [rsp+28h] [rbp-138h]
  int v38; // [rsp+30h] [rbp-130h]
  __int64 v39; // [rsp+38h] [rbp-128h]
  int v40; // [rsp+40h] [rbp-120h]
  int v41; // [rsp+48h] [rbp-118h]
  __int64 v42; // [rsp+50h] [rbp-110h]
  int v43; // [rsp+58h] [rbp-108h]
  int v44; // [rsp+60h] [rbp-100h]
  __int64 v45; // [rsp+68h] [rbp-F8h]
  int v46; // [rsp+70h] [rbp-F0h]
  int v47; // [rsp+78h] [rbp-E8h]
  __int64 v48; // [rsp+80h] [rbp-E0h]
  int v49; // [rsp+88h] [rbp-D8h]
  int v50; // [rsp+90h] [rbp-D0h]
  __int64 v51; // [rsp+98h] [rbp-C8h]
  __int64 v52; // [rsp+130h] [rbp-30h]

  _R15 = (void *)a1;
  v3 = 0LL;
  if ( a2 )
    v4 = &_objc_msgSend_stret;
  else
    v4 = &_objc_msgSend;
  LOBYTE(v3) = a2 != 0;
  v5 = *(void **)(a1 + 8 * v3);
  v36 = *(void **)(a1 + 8 * v3 + 8);
  v6 = (const char *)(8 * v3);
  if ( (unsigned __int8)v5 & 1 )
  {
    v7 = (((unsigned int)v5 ^ objc_debug_taggedpointer_obfuscator) >> 1) & 7;
    if ( v7 == 7 )
      LOWORD(v7) = (unsigned __int8)(((unsigned int)v5 ^ objc_debug_taggedpointer_obfuscator) >> 4) + 8;
    if ( !(_WORD)v7 )
    {
LABEL_40:
      if ( __e48aedf37b9edb179d065231b52a648b & 0x10 )
        ___forwarding____cold_4();
      v26 = getAtomTarget(v5);
      *(_QWORD *)&v6[a1] = v26;
      __invoking___(
        (__int64)v4,
        a1,
        a1,
        1024,
        0LL,
        v27,
        v32,
        v33,
        (_DWORD)v34,
        (_DWORD)v35,
        (__int64)v36,
        (_DWORD)v37,
        v38,
        v39,
        v40,
        v41,
        v42,
        v43,
        v44,
        v45,
        v46,
        v47,
        v48,
        v49,
        v50,
        v51);
      if ( *(_QWORD *)a1 == v26 )
        *(_QWORD *)a1 = v5;
      goto LABEL_43;
    }
  }
  v35 = v6;
  v37 = v4;
  v33 = a2;
  v8 = object_getClass(v5);
  v6 = (const char *)class_getName(v8);
  if ( (unsigned __int8)class_respondsToSelector(v8, (__int64)"forwardingTargetForSelector:") )
  {
    v9 = _objc_msgSend(v5, "forwardingTargetForSelector:", v36);
    if ( v9 )
    {
      if ( v9 != v5 )
      {
        v4 = v37;
        v6 = v35;
        if ( !((unsigned __int8)v9 & 1) )
          goto LABEL_60;
        v10 = (((unsigned int)v9 ^ objc_debug_taggedpointer_obfuscator) >> 1) & 7;
        if ( v10 == 7 )
          LOWORD(v10) = (unsigned __int8)(((unsigned int)v9 ^ objc_debug_taggedpointer_obfuscator) >> 4) + 8;
        if ( (_WORD)v10 )
        {
LABEL_60:
          *(_QWORD *)&v35[a1] = v9;
          _R15 = 0LL;
          goto LABEL_43;
        }
        v5 = v9;
        goto LABEL_40;
      }
    }
  }
  v37 = (void *(**)(void *, const char *, ...))v5;
  if ( !strncmp(v6, "_NSZombie_", 0xAuLL) )
    goto LABEL_45;
  v35 = (const char *)a1;
  if ( !(unsigned __int8)class_respondsToSelector(v8, (__int64)"methodSignatureForSelector:") )
  {
    v29 = class_getSuperclass(v8);
    object_getClassName(v37);
    if ( v29 )
    {
      v11 = CFSTR("*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead");
    }
    else
    {
      object_getClassName(v37);
      v11 = CFSTR("*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?");
    }
    CFLog(4);
LABEL_50:
    v30 = sel_getName(v36, v11);
    if ( (void *)sel_getUid(v30) != v36 )
      CFLog(4);
    v31 = object_getClass(v37);
    if ( !(unsigned __int8)class_respondsToSelector(v31, (__int64)"doesNotRecognizeSelector:") )
      ___forwarding____cold_2(v37);
    _objc_msgSend(v37, "doesNotRecognizeSelector:", v36);
    BUG();
  }
  v11 = (const __CFString *)"methodSignatureForSelector:";
  v12 = _objc_msgSend(v37, "methodSignatureForSelector:", v36);
  if ( !v12 )
    goto LABEL_50;
  v13 = v12;
  v14 = (__int64 *)_objc_msgSend(v12, "_frameDescriptor");
  if ( (((unsigned int)*(unsigned __int16 *)(*v14 + 34) >> 6) & 1) != v33 )
  {
    sel_getName(v36, "_frameDescriptor");
    *(_WORD *)(*v14 + 34);
    CFLog(4);
  }
  v15 = object_getClass(v37);
  v34 = v14;
  if ( (unsigned __int8)class_respondsToSelector(v15, (__int64)"_forwardStackInvocation:") )
  {
    if ( ___forwarding____onceToken != -1 )
      dispatch_once(&___forwarding____onceToken, &__block_literal_global_43);
    v16 = _objc_msgSend(&OBJC_CLASS___NSInvocation, "requiredStackSizeForSignature:", v13);
    v17 = alloca(__chkstk_darwin(&OBJC_CLASS___NSInvocation));
    v6 = (const char *)&v32;
    __bzero(&v32);
    v18 = alloca(__chkstk_darwin(&v32));
    objc_constructInstance(___forwarding____invClass, &v32);
    v36 = v16;
    _objc_msgSend(&v32, "_initWithMethodSignature:frame:buffer:size:", v13, v35, &v32, v16);
    _objc_msgSend(v37, "_forwardStackInvocation:", &v32);
    v19 = 1;
  }
  else
  {
    v20 = object_getClass(v37);
    if ( !(unsigned __int8)class_respondsToSelector(v20, (__int64)"forwardInvocation:") )
      ___forwarding____cold_3((char *)&v38, (__int64)v37);
    v6 = (const char *)_objc_msgSend(&OBJC_CLASS___NSInvocation, "_invocationWithMethodSignature:frame:", v13, v35);
    _objc_msgSend(v37, "forwardInvocation:", v6);
    v36 = 0LL;
    v19 = 0;
  }
  if ( v6[52] && *(_BYTE *)(*v34 + 34) < 0 )
  {
    v21 = *v34;
    memmove(
      *(void **)&v35[*(unsigned int *)(v21 + 28) + *(unsigned __int8 *)(v21 + 32)],
      *(const void **)(*(unsigned __int8 *)(v21 + 32) + *((_QWORD *)v6 + 1) + *(unsigned int *)(v21 + 28)),
      *(unsigned int *)(*(_QWORD *)v21 + 16LL));
  }
  v22 = (char *)_objc_msgSend(v13, "methodReturnType");
  v23 = v22;
  v24 = *v22;
  if ( v24 != 118 && (v24 != 86 || v23[1] != 118) )
  {
    _R15 = (void *)*((_QWORD *)v6 + 2);
    if ( v19 )
    {
      v25 = _objc_msgSend(&OBJC_CLASS___NSData, "dataWithBytes:length:", *((_QWORD *)v6 + 2), v36);
      _R15 = _objc_msgSend(v25, "bytes");
      _objc_release(v6);
      v24 = *v23;
    }
    if ( v24 == 68 )
      __asm { fld     tbyte ptr [r15] }
  }
  else
  {
    _R15 = &___forwarding____placeholder;
    if ( v19 )
      _objc_release(v6);
  }
LABEL_43:
  if ( __stack_chk_guard != v52 )
LABEL_45:
    ___forwarding____cold_1(v37, v6, v36);
  return _R15;
}

流程图梳理

__forwarding__流程分析.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容