iOS-Runtime4-objc_msgSend源码解析

一. 三大阶段

1. 消息发送

消息发送.png

2. 动态方法解析

动态方法解析.png

3. 消息转发

消息转发.png

二. 整体分析

可能是因为objc_msgSend调用次数太多,苹果为了效率,使用了更底层的汇编来实现objc_msgSend。

ENTRY就是个宏,定义了objc_msgSend的入口,下面代码就是objc_msgSend的底层实现。我们一步一步解析objc_msgSend底层做了什么事。

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START

//x0是寄存器:里面放的是receiver,就是person对象
cmp x0, #0                  //步骤1:判断它是否小于等于0,如果小于等于0,就是person不存在,就跳LNilOrTagged
    b.le    LNilOrTagged    //跳LNilOrTagged
    ldr x13, [x0]       // x13 = isa
    and x16, x13, #ISA_MASK // x16 = class  
LGetIsaDone:
    CacheLookup NORMAL      //步骤3:receiver如果receiver不为空,调用CacheLookup,传入NORMAL,查找缓存

LNilOrTagged:
    b.eq    LReturnZero     //步骤2:receiver不存在就返回0

    // tagged
    mov x10, #0xf000000000000000
    cmp x0, x10
    b.hs    LExtTag
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone

LExtTag:
    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone
    
LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    MESSENGER_END_NIL
    ret

    END_ENTRY _objc_msgSend

上面的汇编主要是判断消息接受者是否为空,如果为空就返回0,如果不为空,进入下面,查找缓存:

看到这里,我想到了以前我回调代理的时候都会加这个判断“if (self.delegate && [self.delegate respondsToSelector]) {}”,我同事说没必要加这个判断,现在我明白了,不加判断的确也没事,因为直接返回0,就退出了,但是我还是习惯加个判断。

//步骤4:
.macro CacheLookup
    // x1 = SEL, x16 = isa
    ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
    and w12, w1, w11        // x12 = _cmd & mask //步骤5:根据方法名&mask得到索引,进行查找缓存
    add x12, x10, x12, LSL #4   // x12 = buckets + ((_cmd & mask)<<4)

    ldp x9, x17, [x12]      // {x9, x17} = *bucket
1:  cmp x9, x1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: x12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp x12, x10        // wrap if bucket == buckets
    b.eq    3f
    ldp x9, x17, [x12, #-16]!   // {x9, x17} = *--bucket
    b   1b          // loop

3:  // wrap: x12 = first bucket, w11 = mask
    add x12, x12, w11, UXTW #4  // x12 = buckets+(mask<<4)

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp x9, x17, [x12]      // {x9, x17} = *bucket
1:  cmp x9, x1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp //步骤6:如果查找到缓存就返回

2:  // not hit: x12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0 //步骤7:查找不到缓存就跳CheckMiss
    cmp x12, x10        // wrap if bucket == buckets
    b.eq    3f
    ldp x9, x17, [x12, #-16]!   // {x9, x17} = *--bucket
    b   1b          // loop

3:  // double wrap
    JumpMiss $0
    
.endmacro

上面汇编主要判断能不能查到缓存方法,查到缓存方法就取出,返回方法实现,查不到缓存就进入如下代码:

//步骤7.5:查不到缓存
.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz x9, LGetImpMiss
.elseif $0 == NORMAL //步骤8:调用__objc_msgSend_uncached
    cbz x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

上面汇编,再进入如下代码:

//步骤9:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search

//步骤10:调用MethodTableLookup
MethodTableLookup
br  x17

END_ENTRY __objc_msgSend_uncached

上面汇编,再进入如下代码:

//方法表格查找
//步骤11:
.macro MethodTableLookup
    
    // push frame
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // receiver and selector already in x0 and x1
    mov x2, x16
//步骤12:
//跳转调用__class_lookupMethodAndLoadCache3
//汇编没实现这个方法,所以搜索c语言_class_lookupMethodAndLoadCache3,(汇编的多了一个_)
    bl  __class_lookupMethodAndLoadCache3

    // imp in x0
    mov x17, x0
    
    // restore registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16

.endmacro

上面汇编跳转到c函数_class_lookupMethodAndLoadCache3,如下:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    //步骤13:
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

再跳转到函数:lookUpImpOrForward(查找方法实现或者消息转发),如下:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();
    //步骤14:
    //刚才汇编里面已经查找过缓存了,这里传的是NO
    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    
//步骤41:动态方法解析完成会再次回到这里,再次走一遍objc_msgSend的第一个阶段:消息发送阶段,如果你动态添加方法,这次就一定会调用成功
 retry:    
    runtimeLock.assertReading();

    // Try this class's cache.
    //步骤15:
    //再找一次缓存,怕你动态添加了一些方法
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
        //步骤16:
        //将类对象/元类对象和方法名放进去
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            //步骤21:如果找到这个方法,就填充缓存
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    //步骤25:如果自己类对象里面没找到方法
    // Try superclass caches and method lists.去父类的缓存和方法列表里面找
    {
        unsigned attempts = unreasonableClassCount();
        //这个for循环就是通过superclass遍历所有的父类
        for (Class curClass = cls->superclass; //步骤26:拿到父类
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);//步骤27:去父类缓存里面找
            if (imp) { //如果找到方法
                if (imp != (IMP)_objc_msgForward_impcache) {
//步骤28:将父类缓存里的方法填充到当前类对象的缓存里面,现在就是父类缓存和当前类对象缓存都有这个方法
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            //步骤29:去父类的方法列表里面找
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                //步骤30:如果父类方法列表里面找到,又去填充缓存
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done; //找到,就去done
            }
        }
    }
    //步骤32:到上面的for循环结束objc_msgSend的第一个阶段:消息发送阶段,已经完成

    // No implementation found. Try method resolver once.动态方法解析阶段

    //如果以前没有进行动态解析过才进行动态解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);//步骤33:进入动态方法解析阶段
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
//步骤39.5:第33步完成就会标记为YES(不管你有没有自己实现动态解析的那两个方法),下次就不会动态解析了
        triedResolver = YES;
        goto retry;//步骤40:动态方法解析完成,再次进入上面retry
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.使用消息转发

    //步骤42:没找到方法,并且动态方法解析过了,会进入到消息转发阶段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

    //步骤31:
    //如果找到方法,就直接return imp,然后返回到汇编代码里面去调用
 done:
    runtimeLock.unlockRead();

    return imp;
}

看上面函数名也能看出来,lookUpImpOrForward函数就是objc_msgSend函数的核心实现,包括了消息发送,动态方法解析,消息转发。

下面我们一步一步分析:

三. 详细分析

1. 消息发送

① 先查找当前类对象或者元类对象

// Try this class's cache.
//步骤15:
//再找一次缓存,怕你动态添加了一些方法
imp = cache_getImp(cls, sel);
if (imp) goto done;

// Try this class's method lists.
{
    //步骤16:
    //将类对象/元类对象和方法名放进去
    Method meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        //步骤21:如果找到这个方法,就填充缓存
        log_and_fill_cache(cls, meth->imp, sel, inst, cls);
        imp = meth->imp;
        goto done;
    }
}

可以看出,虽然汇编代码里面已经查过缓存了,但是这里又查了一次缓存,就是怕你动态添加了一些方法。然后进入getMethodNoSuper_nolock函数,这个函数就是在当前类对象或者元类对象的方法列表(methods数组)里面查找方法(注意是当前),如果查到方法了就拿到方法的imp,goto done返回到汇编。

下面进入getMethodNoSuper_nolock函数看看究竟是怎么在类对象或者元类对象的方法列表里面查找方法的,如下:

getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?
    
    //很明显是拿到class_rw_t里面的methods数组进行搜索
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        //步骤17:搜索
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

上面代码,很明显是先拿到class_rw_t里面的methods数组,然后进入search_method_list:

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        //步骤18:如果方法排好序了先去排好序的方法里面找
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        //步骤20:
        //没有排好序就线性比较,根据方法名,一个一个比较
        // Linear search of unsorted method list
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }
}

上面代码,如果方法列表没排序就遍历,如果方法列表是排序过的,就进入findMethodInSortedMethodList

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    //步骤19:
    //排好序的用二分查找,效率更高
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    return nil;
}

上面函数使用了二分法查找方法。

现在我们知道了,如果方法列表排序过了就会使用二分法查找,更快速,如果没排序过就直接遍历。

如果在当前类对象或者元类对象找到方法,就会填充缓存,然后把imp返回给汇编。
如果在当前类对象或者元类对象没有找到方法,就会循环遍历父类,如下:

② 再循环遍历父类

//步骤25:如果自己类对象里面没找到方法
    // Try superclass caches and method lists.去父类的缓存和方法列表里面找
    {
        unsigned attempts = unreasonableClassCount();
        //这个for循环就是通过superclass遍历所有的父类
        for (Class curClass = cls->superclass; //步骤26:拿到父类
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);//步骤27:去父类缓存里面找
            if (imp) { //如果找到方法
                if (imp != (IMP)_objc_msgForward_impcache) {
//步骤28:将父类缓存里的方法填充到当前类对象的缓存里面,现在就是父类缓存和当前类对象缓存都有这个方法
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            //步骤29:去父类的方法列表里面找
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                //步骤30:如果父类方法列表里面找到,又去填充缓存
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done; //找到,就去done
            }
        }
    }
    //步骤32:到上面的for循环结束objc_msgSend的第一个阶段:消息发送阶段,已经完成

上面的代码,循环遍历父类,拿到一个父类:
先从父类的缓存里面找,如果找到方法,就把父类缓存里面的方法填入当前类对象或者元类对象的缓存,然后结束。
再从父类的方法列表里面找(和当前类对象或者元类对象一样,也是调用getMethodNoSuper_nolock函数),如果找到方法,就把父类的方法列表里面的方法填入当前类对象或者元类对象的缓存,然后结束。

如果上面整个循环都完成,还是没有找到方法就会进入第二阶段:动态方法解析

2. 动态方法解析

// No implementation found. Try method resolver once.动态方法解析阶段
    //如果以前没有进行动态解析过才进行动态解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);//步骤33:进入动态方法解析阶段
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
//步骤39.5:第33步完成就会标记为YES(不管你有没有自己实现动态解析的那两个方法),下次就不会动态解析了
        triedResolver = YES;
        goto retry;//步骤40:动态方法解析完成,再次进入上面retry
    }

上面函数传入了triedResolver,默认为NO,表示没进行过动态方法解析,然后进入动态方法解析,解析完成后会设置triedResolver = YES;表示动态方法解析过,这样下次就不会再进入动态方法解析了,然后会goto retry,回到第一阶段:消息发送阶段,重新查找一次方法(这时候如果我们动态添加方法了,那么再来到阶段一就能查找到方法了)。

进入动态方法解析函数_class_resolveMethod:

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);//步骤34:不是元类对象,会调用上面的方法,动态添加实例方法
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);//步骤37:是元类对象,会调用上面的方法,动态添加类方法,
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

上面代码,如果不是元类对象,会调用[cls resolveInstanceMethod:sel]方法,进行动态添加实例方法,这个方法是我们自己实现的。
如果是元类对象,会调用[nonMetaClass resolveClassMethod:sel]方法,进行动态添加类方法,这个方法也是我们自己实现的。

注意:动态添加方法是把方法添加到类对象或者元类对象的方法列表里面了(methods数组)。

自此,第二阶段完成。如果消息发送没找到方法,并且动态方法解析过了也没找到方法,会进入第三阶段:消息转发

3. 消息转发

imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

消息转发会调用_objc_msgForward_impcache函数,之后再填充缓存,在汇编中找到_objc_msgForward_impcache函数的实现,如下:

STATIC_ENTRY __objc_msgForward_impcache

MESSENGER_START
nop
MESSENGER_END_SLOW

// No stret specialization.
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

上面函数会再调用__objc_msgForward

ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br  x17

END_ENTRY __objc_msgForward

最后只找到_objc_forward_handler是个指向函数的指针,说明会调用这个指针指向的函数,但是指向哪个函数就不知道了,因为后面的代码不开源了。

void *_objc_forward_handler = nil;

或者你可以打断点,一步一步查看汇编,看消息转发底层是怎么实现的,但是这样难度比较大还比较麻烦。这里有一份国外开发者根据汇编代码加上自己的理解变成C语言实现的一份伪代码:__forwarding__.c文件,但是这个文件还是比较难以理解,这里有一份精简版:__forwarding__clean.c文件。

int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        //拿到forwardingTarget
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            //给forwardingTarget发送sel消息
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        //获取方法签名
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

            //如果方法签名不为空,会调用forwardInvocation
            [receiver forwardInvocation:invocation];

            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue;
        }
    }

    //如果上面两个方法返回值都是nil,会调用doesNotRecognizeSelector方法
    if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }

    // The point of no return.
    kill(getpid(), 9);
}

上面代码可以看出:

  1. 首先判断类的forwardingTargetForSelector方法是否有返回值,如果有返回值,就拿到这个方法的返回值和SEl传给objc_msgSend(其实就是调用forwardingTargetForSelector返回对象的实例方法或者类方法)。
  2. 如果forwardingTargetForSelector方法返回值为空,就调用methodSignatureForSelector方法,这个方法返回一个方法签名(返回值类型、参数类型)。如果方法签名不为空,就会调用forwardInvocation方法,在forwardInvocation方法里面我们可以做任何我们想做的事。
  3. 如果methodSignatureForSelector返回的也是空,就调用doesNotRecognizeSelector报错:unrecognized selector sent to instance/class。

小疑问:

上面伪代码首先会调用__forwarding__函数,为什么会先调用这个函数呢?当调用一个对象没实现的方法时会报如下错:

2019-12-09 10:10:29.712307+0800 Interview01-消息转发[26773:2047187] -[MJPerson test]: unrecognized selector sent to instance 0x100601e80
2019-12-09 10:10:29.769039+0800 Interview01-消息转发[26773:2047187] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MJPerson test]: unrecognized selector sent to instance 0x100601e80'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff53be2cfd __exceptionPreprocess + 256
    1   libobjc.A.dylib                     0x00007fff7e2aea17 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff53c5cb06 -[NSObject(NSObject) __retain_OA] + 0
    3   CoreFoundation                      0x00007fff53b84b8f ___forwarding___ + 1485
    4   CoreFoundation                      0x00007fff53b84538 _CF_forwarding_prep_0 + 120
    
    6   libdyld.dylib                       0x00007fff7fa7c3d5 start + 1
    7   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

可以发现会先调用CoreFoundation框架的_CF_forwarding_prep_0,然后再调用CoreFoundation框架的___forwarding___函数。

源码地址:objc4

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