iOS-objc_msgSend

670 阅读12分钟

问题先行

1、OC中调用方法的本质是?
2、OC中调用方法的流程是如何?
3、方法查找流程中方法的比对,会判断是类方法还是实例方法吗?
4、如下代码执行会crash吗?

@interface Son : NSObject
+ (void)test;
@end
@implementation Son
@end@interface NSObject (Test)
@end
@implementation NSObject (Test)
- (void)test {
}
@end/// 这个代码执行后会crash吗?
[Son test];

5、可以用C语言实现一个二分查找吗?
6resolveInstanceMethodresolveClassMethod为什么会在相应方法没有实现的时候调用两次?

资料准备

1、objc源码下载opensource.apple.com/

初步探索

通过clang命令,生成相关代码的C++文件,查看底层实现。

clang -rewrite-objc main.m -o main.cpp

实例

int main(int argc, char * argv[]) {
    @autoreleasepool {
        ASTest *test = [[ASTest alloc]init];
        [test run];
        return 0;
    }
}
​
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        ASTest *test = ((ASTest *(*)(id, SEL))(void *)objc_msgSend)((id)((ASTest *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ASTest"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)test, sel_registerName("run"));
        return 0;
    }
}

通过Xcode的符号断点可以看到如下流程

首先在方法出打一个断点,然后在设置一个objc_msgSend的符号断点,然后读取寄存器的值确认,就可以得到如下结果。 结论
1、通过以上可以看出OC方法调用底层的本质就是通过objc_msgSend来发送消息
2objc_msgSend两个固定个参数:id self(消息接收者)、SEL sel(方法编号),不固定参数:id(方法参数)

消息查找流程其实是通过上层的方法编号sel发送消息objc_msgSend找到具体实现imp的过程
objc_msgSend是用汇编写成的,为什么不用C而是用汇编写?
C语言不能通过写一个函数,保留未知的参数,跳转到任意的指针,而汇编有寄存器 对于一些调用频率太高的函数或操作,使用汇编来实现能够提高效率和性能,容易被机器来识别

objc_msgSend流程源码探索

开始查找

// 1️⃣ 消息发送 --- 汇编入口 ---
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// 2️⃣ p0与空对比,判断接受者是否存在。p0为_objc_msgSend的第一个参数
cmp  p0, #0      // nil check and tagged pointer check
// 3️⃣ --- le小于 --- taggedpointer(小对象类型)流程
#if SUPPORT_TAGGED_POINTERS
  b.le  LNilOrTagged    //  (MSB tagged pointer looks negative)
#else
  b.eq  LReturnZero
#endif
// 4️⃣ 根据对象取出isa,即从x0寄存器指向的地址取出isa,存入p13寄存器
ldr  p13, [x0]    // p13 = isa
// 5️⃣ 通过 p16(p13 & ISA_MASK)取到shiftcls信息,得到class信息。
GetClassFromIsa_p16 p13    // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 6️⃣ 如果有isa,走到CacheLookup,即缓存查找流程(sel-imp快速查找流程)
CacheLookup NORMAL, _objc_msgSend

1、开始objc_msgSend
2、判断消息接收者是否为空,为空直接返回
3、判断tagged_pointers 
4、取得对象中的isa存一份到p13中
5、根据isa进行mask地址偏移得到对应的上级对象(类、元类)
6、开始在缓存中查找imp——开始了快速查找流程

快速查找流程

 /********************************************************************
.......
//1️⃣ 此时x1是sel,x16是class
 * Takes:
 *   x1 = selector
 *   x16 = class to be searched
.......
 ********************************************************************/
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.......
.macro CacheLookup
​
LLookupStart$1:
​​
//2️⃣ 通过cache首地址平移16字节(因为在objc_class中,首地址距离cache正好16字节,即isa首地址 占8字节,superClass占8字节)获取cahce,cache中高16位存mask,低48位存buckets,即p11 = cache
// p1 = SEL, p16 = isa
ldr  p11, [x16, #CACHE]        // p11 = mask|buckets#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//3️⃣ 从cache中分别取出buckets和mask,并由mask根据哈希算法计算出哈希下标
and  p10, p11, #0x0000ffffffffffff  // p10 = buckets
and  p12, p1, p11, LSR #48    // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
and  p10, p11, #~0xf      // p10 = buckets
and  p11, p11, #0xf      // p11 = maskShift
mov  p12, #0xffff
lsr  p11, p12, p11        // p11 = mask = 0xffff >> p11
and  p12, p1, p11        // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif//4️⃣ 根据所得的哈希下标index 和 buckets首地址,取出哈希下标对应的bucket
  add  p12, p10, p12, LSL #(1+PTRSHIFT)
                 // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))//5️⃣ 根据获取的bucket,取出其中的imp存入p17,即p17 = imp,取出sel存入p9,即p9 = sel
  ldp  p17, p9, [x12]    // {imp, sel} = *bucket
//6️⃣ 第一次递归循环比较 sel、_cmd,CacheHit || CheckMiss
1:  cmp  p9, p1      // if (bucket->sel != _cmd)
  b.ne  2f      //     scan more
  CacheHit $0      // call or return imp2:  // not hit: p12 = not-hit bucket
  CheckMiss $0      // miss if bucket->sel == 0
  cmp  p12, p10    // wrap if bucket == buckets
  b.eq  3f
  ldp  p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
  b  1b      // loop3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
  add  p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
          // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
  add  p12, p12, p11, LSL #(1+PTRSHIFT)
          // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
​
ldp  p17, p9, [x12]    // {imp, sel} = *bucket
//7️⃣ 第二次递归循环比较 sel、_cmd,CacheHit || CheckMiss
1:  cmp  p9, p1      // if (bucket->sel != _cmd)
b.ne  2f      //     scan more
CacheHit $0      // call or return imp2:  // not hit: p12 = not-hit bucket
CheckMiss $0      // miss if bucket->sel == 0
cmp  p12, p10    // wrap if bucket == buckets
b.eq  3f
ldp  p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
b  1b      // loop
​
LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
  JumpMiss $0
​
.endmacro
​

1、此时x1selx16class
2、通过cache首地址平移16字节(因为在objc_class中,首地址距离cache正好16字节,即isa首地址 占8字节,superClass占8字节),获取cahcecache中高16位存mask,低48位存buckets,即p11 = cache
3、从cache中分别取出bucketsmask,并由mask根据哈希算法计算出哈希下标

  • 通过cache和掩码(即0x0000ffffffffffff)的&运算,将高16位mask抹零,得到buckets指针地址,即p10=buckets
  • cache右移48位,得到mask,即p11=mask
  • objc_msgSend的参数p1(即第二个参数_cmd)& msak,通过哈希算法,得到需要查找存储sel-impbucket下标index,即p12=index=_cmd&mask,为什么通过这种方式呢?因为在存储sel-imp时,也是通过同样哈希算法计算哈希下标进行存储,所以读取也需要通过同样的方式读取。 4、根据所得的哈希下标indexbuckets首地址,取出哈希下标对应的bucket
  • 其中PTRSHIFT等于3,左移4位(即2^4=16字节)的目的是计算出一个bucket实际占用的大小,结构体bucket_tsel占8字节,imp占8字节
  • 根据计算的哈希下标index乘以单个bucket占用的内存大小,得到buckets首地址在实际内存中的偏移量
  • 通过首地址+实际偏移量,获取哈希下标index对应的bucket 5、根据获取的bucket,取出其中的imp存入p17,即p17=imp,取出sel存入p9,即p9=sel
    6、第一次递归循环
  • 比较获取的bucketselobjc_msgSend的第二个参数的_cmd(即p1)是否相等
  • 如果相等,则直接跳转至CacheHit,即缓存命中,返回imp
  • 如果不相等,有以下两种情况
    • 如果一直都找不到,直接跳转至CheckMiss,因为$0是normal,会跳转至__objc_msgSend_uncached,即进入慢速查找流程
    • 如果根据index获取的bucket等于buckets的第一个元素,则人为的将当前bucket设置为buckets的最后一个元素(通过buckets首地址+mask右移44位(等同于左移4位)直接定位到bucker的最后一个元素),然后继续进行递归循环(第一个递归循环嵌套第二个递归循环),即⑥
    • 如果当前bucket不等于buckets的第一个元素,则继续向前查找,进入第一次递归循环 7、第二次递归循环:重复6的操作,与6中唯一区别是,如果当前的bucket还是等于buckets的第一个元素,则直接跳转至JumpMiss,此时的$0是normal,也是直接跳转至__objc_msgSend_uncached,即进入慢速查找流程
      8、在快速查找流程中,如果没有找到方法实现,无论是走到CheckMiss还是JumpMiss,最终都会走到__objc_msgSend_uncached汇编函数,其中的核心是MethodTableLookup(即查询方法列表),其中的核心是_lookUpImpOrForward

慢速查找流程

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    /// 缓存检查,若存在直接获取IMP
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();
    /// 确保cls是初始化过的
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }
    /// 加锁保证读取的线程安全
    runtimeLock.lock();

    /// 判断当前是否是一个已知的类
    checkIsKnownClass(cls);
    /// 判断类是否实现,如果没有实现,需要先实现。
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    /// 当前类对象中方法查找、若找到进行缓存
    /// 沿着继承链查找父类的类对象中方法查找,若找到进行缓存
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            /// 查找(二分查找)方法,如果找到则返回,将方法缓存到cache中
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        /// 父类的缓存
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    /// 没有找到方法实现,尝试一次方法解析
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        /// 存储到缓存
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    /// 解锁
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

lookUpImpOrForward方法大体有以下功能

1、缓存检查,若存在直接获取IMP
2、确保cls是初始化过的
3、判断类是否实现,如果没有实现,需要先实现,路径为realizeAndInitializeIfNeeded_locked->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift(实现类)
4、当前类对象中方法查找、若找到进行缓存
5沿着继承链查找父类的类对象中方法查找,若找到进行缓存
6、没有找到方法实现,尝试一次方法解析

二分查找源码

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);
    auto first = list->begin();
    auto base = first;
    decltype(first) probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        uintptr_t probeValue = (uintptr_t)getName(probe);
        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)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    return nil;
}
  • count >>= 1
  • 右移一位相当于减半,例如:1000(8) 右移 0100(4);0111(7) 右移 0011(3)
  • probe = base + (count >> 1);
  • probe = 初始位置为 + 一半的位置
  • 如果目标值小于一半位置的值,则初始位置不变,结尾位置再次右移一位减半
  • 如果目标值大于一半位置的值,则初始位置变为一半位置加+1,count减1

总结
1、对于实例方法,即在类中查找,其慢速查找的父类链是:类--父类--根类(NSObjcet)--nil
2、对于类方法,即在元类中查找,其慢速查找的父类链是:元类--根元类--根类(NSObjcet)--nil
3、如果快速查找、慢速查找也没有找到方法实现,则尝试消息动态决议
4、如果消息动态决议仍然没有找到,则进行消息转发

objc_msgSendSuper

1、构造结构体
2、结构体中存在objc_super如下
3、消息响应载体依然是self

OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
​
    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

类方法和对象方法

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // This deliberately avoids +initialize because it historically did so.
    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.
#warning fixme build and search caches
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
    return _class_getMethod(cls, sel);
}

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

通过源码可以看出,类方法本质是获取元类的对象方法,元类的对象方法上层就是通过类的类方法体现。

消息日志打印

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

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

通过log_and_fill_cache方法可以看出,在符合条件的情况下会在logMessageSend方法中进行相关日志本地输出。关键条件就是objcMsgLogEnabled字段的值。

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

通过上面方法即可设置objcMsgLogEnabled字段的值。因此可以如下进行消息日志打印

#import <Foundation/Foundation.h>
#import "Sun.h"

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        instrumentObjcMessageSends(YES);
        Sun *objc2 = [Sun alloc];
        [objc2 run];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

消息动态决议流程

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

其流程如下

总结

1、实例方法可以重写resolveInstanceMethod添加imp
2、类方法可以在本类重写resolveClassMethod往元类添加imp
3、消息动态决议只要在任意一步lookUpImpOrNil查找到imp就不会查找下去——即本类做了消息动态决议,不会走到NSObjct分类的动态方法解析
4、所有方法都可以通过在NSObject分类重写resolveInstanceMethod添加imp解决崩溃

所有崩溃都在NSObjct分类中处理是否合理?
1、统一处理起来耦合度高
2、逻辑判断多
3、SDK封装的时候需要给一个容错空间

消息转发流程

快速转发

/// 实例方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return [[Son alloc]init];
}
/// 类方法
+ (id)forwardingTargetForSelector:(SEL)aSelector {
    return Son.class;
}

1、该方法的返回对象是执行sel的新对象,也就是自己处理不了会将消息转发给别的对象进行相关方法的处理,但是不能返回self,否则会一直找不到
2、该方法的效率较高,如果不实现,会走到forwardInvocation:方法进行处理
3、底层会调用objc_msgSend(forwardingTarget, sel, ...),来实现消息的发送
4、被转发消息的接受者参数、返回值等应和原方法相同 \

慢速转发

/// 实例方法
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    /// 转发处理
    [anInvocation invokeWithTarget:[[Son alloc]init]];
    /// 也可以不进行处理,只是错误上报,程序也不会崩溃
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

/// 类方法
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    /// 转发处理
    [anInvocation invokeWithTarget:Son.class];
    /// 也可以不进行处理,只是错误上报,程序也不会崩溃
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

1、forwardInvocation和methodSignatureForSelector必须是同时存在的,底层会通过方法签名,生成一个NSInvocation,将其作为参数传递调用
2、查找可以响应NSInvocation中编码的消息的对象(对于所有消息,此对象不必相同)
3、使用anInvocation将消息发送到该对象,anInvocation将保存结果,运行时系统将提取结果并将其传递给原始发送者
4、无论在forwardInvocation方法中是否处理invocation事务,程序都不会崩溃。
5、类方法在NSObject中找不到,只需要按照👆修改即可。

总结

1、【快速查找流程】首先,在类的缓存cache中查找指定方法的实现
2、【慢速查找流程】如果缓存中没有找到,则在类的方法列表中查找,如果还是没找到,则去父类链的缓存和方法列表中查找
3、【动态方法决议】如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod方法
4、【消息转发】如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发
5、如果转发之后也没有,则程序直接报错崩溃unrecognized selector sent to instance

问题先行解答

1、OC中调用方法的本质是?
objc_msgSend消息发送
2、OC中调用方法的流程是如何?
详细流程如上分析。
3、方法查找流程中方法的比对,会判断是类方法还是实例方法吗?
不会,见如下源码,仅仅是比对method_name和sel是否相等

/* These next three functions are the heart of ObjC method lookup. 
 * If the class is currently in use, methodListLock must be held by the caller.
 */
static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {
    int i;
    if (!mlist) return nil;
    for (i = 0; i < mlist->method_count; i++) {
        old_method *m = &mlist->method_list[i];
        if (m->method_name == sel) {
            return m;
        }
    }
    return nil;
}

4、如下代码执行会crash吗?
不会,对于类方法,即在元类中查找,其慢速查找的父类链是:元类 -> 元类的父类 -> 。。。 -> 根元类 -> NSObject -> nil
5、可以用C语言实现一个二个查找吗?

static int balance[9] = {1,2,4,6,8,11,34,67,88};
static int len = sizeof(balance) / sizeof(int);

int search(int keyValue) {
    int base = 0;
    int probe;
    int count;
    for (count = len; count != 0; count >>= 1) {
        probe = base + (count >>= 1);
        int probeVlaue = balance[probe];
        if (keyValue == probeVlaue) {
            return probe;
        }
        if (keyValue > probeVlaue) {
            base = probe + 1;
            count --;
        }
    }
    return 0;
}

6resolveInstanceMethodresolveClassMethod为什么会在相应方法没有实现的时候调用两次?
待解决