iOS 消息发送、动态方法解析和消息转发 objc4-838.1源码

苹果官方资源opensource
objc4-838可编译联调源码

本章节研究消息发送和消息转发:
1.方法调用的实质
2.objc_msgSend和objc_msgSendSuper的区别
3.消息发送的快速查找imp过程
4.消息发送的慢速查找imp过程
5.动态方法解析过程
6.消息快速转发
7.消息慢速转发
8.动态方法解析案例、消息转发案例

本文核心疑问:怎么通过sel找到imp

前言

我们知道Objctive-C方法调用实际是给一个对象发送消息。从这句话我们不难看出必要的条件有消息的接收者,和怎么发送消息。
消息的发送过程是通过sel找到imp

Runtime框架是一套运行时api,底层是用c/c++/汇编写的,提供给Objective-C/swift等使用。Runtime分成两部分:1.类的各方面的动态配置、2.消息发送。

一、方法调用的实质

工程准备:新建一个工程

新建工程.png

工程开启消息发送配置:target -> Build Settings -> Apple Clang - Preprocessing -> Enable Strict Checking of objc_msgSend Call 设置为NO

声明一个WJPerson

#import 
NS_ASSUME_NONNULL_BEGIN
@interface WJPerson : NSObject
- (void)run;
- (void)walk;
+ (void)say;
@end
NS_ASSUME_NONNULL_END
#import "WJPerson.h"
@implementation WJPerson
- (void)run{
    NSLog(@"%s",__func__);
}
- (void)walk{
    NSLog(@"%s",__func__);
}
+ (void)say {
    NSLog(@"%s",__func__);
}
@end

main.m

#import 
#import "WJPerson.h"
#import 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        WJPerson *p = [[WJPerson alloc] init];
        [p run];
    }
    return 0;
}

得到编译后的main.cpp
打开终端cdmain.m路径

// 编译
$ clang -rewrite-objc main.m -o main.cpp
main.cpp文件

方法调用默认会传递两个隐藏的参数:1.消息接收者id、2.消息方法名SEL
方法调用的实质是objc_msgSend函数给对象发送消息

二、objc_msgSend和objc_msgSendSuper的区别

objc_msgSendSuper是发消息给对象的父类的时候就会编译出来。

来看看这么一个案例:
声明一个MyPerson类

#import 
@interface MyPerson : NSObject
-(void)study;
@end
#import "MyPerson.h"
@implementation MyPerson
-(void)study {
    NSLog(@"%s",__func__);
}
@end

声明一个MyTeacher类继承自MyPerson类

#import "MyPerson.h"
@interface MyTeacher : MyPerson
@end
#import "MyTeacher.h"
#import 
@implementation MyTeacher
-(instancetype)init {
    if (self = [super init]) {
        NSLog(@"%@",[self class]);
        NSLog(@"%@",[super class]);
    }
    return self;
}
-(void)study {
     [super study];
}
@end

在ViewContriller里使用这个MyTeacher

#import "ViewController.h"
#import "MyTeacher.h"
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    MyTeacher *t = [[MyTeacher alloc] init];
    [t study];
}
@end

问题:MyTeacher里的init方法里的[self class][super class]调用分别返回的是什么?

来看看self关键字super关键字有区别:
打开终端cdMyTeacher.m路径

$ clang -rewrite-objc MyTeacher.m

找到MyTeacher.cpp的init方法:

[super class]在编译期间会把代码转化成objc_msgSendSuper的方式发送消息,注意消息的接收者依然是self(MyTeacher类对象),而找class方法的imp会超类里找(这里是MyPerson的元类)

方法调用苹果官方文档给出的解释:
当遇到方法调用时,编译器生成对objc_msgSendobjc_msgsend_streetobjc_msgSendSuperobjc_msgsendsuper_street函数之一的调用。
发送到对象的超类(使用super关键字)的消息使用objc_msgSendSuper发送;其他消息使用objc_msgSend发送。将数据结构作为返回值的方法使用objc_msgsendsuper_streetobjc_msgsend_street发送。

自己实现一个MyTeacher里的study方法的[super study]调用;

// MyTeacher.m
- (void)study {
//    [super study];
    struct objc_super lg_objc_super;
    lg_objc_super.receiver = self;
    lg_objc_super.super_class = MyPerson.class;

    void* (*objc_msgSendSuperTyped)(struct objc_super *self,SEL _cmd) = (void *)objc_msgSendSuper;
    objc_msgSendSuperTyped(&lg_objc_super,@selector(study));
}

三、消息发送两种查找imp方式

  • 快速 -> 缓存里找 汇编cache_t 方法实现imp哈希表
  • 慢速 -> c/c++ 找缓存

打开objc4-838源码

四、消息发送的快速查找imp过程(汇编环节)

objc_msgSend 底层在源码上搜,是进入到汇编的源码 ENTRY _objc_msgSend(只看arm64架构下)

objc_msgSend的源码入口
  • 1.当进入消息发送入口时,先判断消息接收者是否存在,不存在则重新执行objc_msgSend

再到LNilOrTagged都做了些什么:

LNilOrTagged

我们先看LReturnZero

LReturnZero
  • 2.检测指针如果为空,就立马返回。结论:给nil发送消息不会做处理

接着回来看看LGetIsaDone做了什么:

进入CacheLookup,是一个宏定义

//在cache中通过sel查找imp的核心流程
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    //
    // Restart protocol:
    //
    //   As soon as we're past the LLookupStart\Function label we may have
    //   loaded an invalid cache pointer or mask.
    //
    //   When task_restartable_ranges_synchronize() is called,
    //   (or when a signal hits us) before we're past LLookupEnd\Function,
    //   then our PC will be reset to LLookupRecover\Function which forcefully
    //   jumps to the cache-miss codepath which have the following
    //   requirements:
    //
    //   GETIMP:
    //     The cache-miss is just returning NULL (setting x0 to 0)
    //
    //   NORMAL and LOOKUP:
    //   - x0 contains the receiver
    //   - x1 contains the selector
    //   - x16 contains the isa
    //   - other registers are set as per calling conventions
    //

//从x16中取出class移到x15中
    mov x15, x16            // stash the original isa
//开始查找
LLookupStart\Function:
    // p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
//ldr表示将一个值存入到p10寄存器中
//x16表示p16寄存器存储的值,当前是Class
//#数值表示一个值,这里的CACHE经过全局搜索发现是2倍的指针地址,也就是16个字节
//#define CACHE (2 * __SIZEOF_POINTER__)
//经计算,p10就是cache
    ldr p10, [x16, #CACHE]              // p10 = mask|buckets
    lsr p11, p10, #48           // p11 = mask
    and p10, p10, #0xffffffffffff   // p10 = buckets
    and w12, w1, w11            // x12 = _cmd & mask
//真机64位看这个
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//CACHE 16字节,也就是通过isa内存平移获取cache,然后cache的首地址就是 (bucket_t *)
    ldr p11, [x16, #CACHE]          // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
//获取buckets
#if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
#else
//and表示与运算,将与上mask后的buckets值保存到p10寄存器
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
//p11与#0比较,如果p11不存在,就走Function,如果存在走LLookupPreopt
    tbnz    p11, #0, LLookupPreopt\Function
#endif
//按位右移7个单位,存到p12里面,p0是对象,p1是_cmd
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
//LSR表示逻辑向右偏移
//p11, LSR #48表示cache偏移48位,拿到前16位,也就是得到mask
//这个是哈希算法,p12存储的就是搜索下标(哈希地址)
//整句表示_cmd & mask并保存到p12
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    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

//去除掩码后bucket的内存平移
//PTRSHIFT经全局搜索发现是3
//LSL #(1+PTRSHIFT)表示逻辑左移4位,也就是*16
//通过bucket的首地址进行左平移下标的16倍数并与p12相与得到bucket,并存入到p13中
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                        // do {

//ldp表示出栈,取出bucket中的imp和sel分别存放到p17和p9
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
//cmp表示比较,对比p9和p1,如果相同就找到了对应的方法,返回对应imp,走CacheHit
    cmp p9, p1              //     if (sel != _cmd) {
//b.ne表示如果不相同则跳转到2f
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
//向前查找下一个bucket,一直循环直到找到对应的方法,循环完都没有找到就调用_objc_msgSend_uncached
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
//通过p13和p10来判断是否是第一个bucket
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b

    // wrap-around:
    //   p10 = first bucket
    //   p11 = mask (and maybe other bits on LP64)
    //   p12 = _cmd & mask
    //
    // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
    // So stop when we circle back to the first probed bucket
    // rather than when hitting the first bucket again.
    //
    // Note that we might probe the initial bucket twice
    // when the first probed slot is the last entry.


#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    add p13, p10, w11, UXTW #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p13, p10, p11, LSL #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b

LLookupEnd\Function:
LLookupRecover\Function:
    b   \MissLabelDynamic

#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
    and p10, p11, #0x007ffffffffffffe   // p10 = buckets
    autdb   x10, x16            // auth as early as possible
#endif

    // x12 = (_cmd - first_shared_cache_sel)
    adrp    x9, _MagicSelRef@PAGE
    ldr p9, [x9, _MagicSelRef@PAGEOFF]
    sub p12, p1, p9

    // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
    // bits 63..60 of x11 are the number of bits in hash_mask
    // bits 59..55 of x11 is hash_shift

    lsr x17, x11, #55           // w17 = (hash_shift, ...)
    lsr w9, w12, w17            // >>= shift

    lsr x17, x11, #60           // w17 = mask_bits
    mov x11, #0x7fff
    lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)
    and x9, x9, x11         // &= mask
#else
    // bits 63..53 of x11 is hash_mask
    // bits 52..48 of x11 is hash_shift
    lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)
    lsr w9, w12, w17            // >>= shift
    and x9, x9, x11, LSR #53        // &=  mask
#endif

    // sel_offs is 26 bits because it needs to address a 64 MB buffer (~ 20 MB as of writing)
    // keep the remaining 38 bits for the IMP offset, which may need to reach
    // across the shared cache. This offset needs to be shifted << 2. We did this
    // to give it even more reach, given the alignment of source (the class data)
    // and destination (the IMP)
    ldr x17, [x10, x9, LSL #3]      // x17 == (sel_offs << 38) | imp_offs
    cmp x12, x17, LSR #38

.if \Mode == GETIMP
    b.ne    \MissLabelConstant      // cache miss
    sbfiz x17, x17, #2, #38         // imp_offs = combined_imp_and_sel[0..37] << 2
    sub x0, x16, x17                // imp = isa - imp_offs
    SignAsImp x0
    ret
.else
    b.ne    5f                      // cache miss
    sbfiz x17, x17, #2, #38         // imp_offs = combined_imp_and_sel[0..37] << 2
    sub x17, x16, x17               // imp = isa - imp_offs
.if \Mode == NORMAL
    br  x17
.elseif \Mode == LOOKUP
    orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
    SignAsImp x17
    ret
.else
.abort  unhandled mode \Mode
.endif

5:  ldursw  x9, [x10, #-8]          // offset -8 is the fallback offset
    add x16, x16, x9            // compute the fallback isa
    b   LLookupStart\Function       // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES

.endmacro
  • 3.通过 类对象/元类 (objc_class) 通过内存平移得到cache,获取buckets,通过内存平移的方式获取对应的方法(对比sel)。

如果找到sel就会进入CacheHit,去return or call imp

CacheHit

如果没有找到sel就会进入__objc_msgSend_uncached

__objc_msgSend_uncached
MethodTableLookup
  • 4.如果对比sel找到了imp,就会return or call imp,如果没有sel,则去调用_lookUpImpOrForward

接下来_lookUpImpOrForward在汇编代码里就找不到了。
至此快速查找imp汇编部分就结束了,接下来到了漫长查找过程:c/c++环节。

总结消息发送快速查找imp(汇编):
objc_msgSend(receiver, sel, ...)
1.检查消息接收者receiver是否存在,为nil则不做任何处理
2.通过receiverisa指针找到对应的class类对象
3.找到class类对象进行内存平移,找到cache
4.从cache中获取buckets
5.从buckets中对比参数sel,看在缓存里有没有同名方法
6.如果buckets中有对应的sel --> cacheHit --> 调用imp
7.如果buckets中没有对应的sel --> _objc_msgSend_uncached -> _lookUpImpOrForward (c/c++慢速查找)

五、消息发送的慢速查找imp过程(c/c++环节)

来看看lookUpImpOrForward函数的实现:

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        // The first message sent to a class is often +new or +alloc, or +self
        // which goes through objc_opt_* or various optimized entry points.
        //
        // However, the class isn't realized/initialized yet at this point,
        // and the optimized entry points fall down through objc_msgSend,
        // which ends up here.
        //
        // We really want to avoid caching these, as it can cause IMP caches
        // to be made with a single entry forever.
        //
        // Note that this check is racy as several threads might try to
        // message a given class for the first time at the same time,
        // in which case we might cache anyway.
        behavior |= LOOKUP_NOCACHE;
    }

    // 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.lock();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    checkIsKnownClass(cls);
    // 确定当前类的继承链关系
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); 
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookup the class's cache again right after
    // we take the lock but for the vast majority of the cases
    // evidence shows this is a miss most of the time, hence a time loss.
    //
    // The only codepath calling into this without having performed some
    // kind of cache lookup is class_getInstanceMethod().

    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
            // 如果是常量优化缓存
            // 再一次从cache查找imp
            // 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            // 每次判断都会把curClass的父类赋值给curClass
            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 // iOS操作系统且真机的情况下
        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.检查类是否被初始化、是否是个已知的关系、确定继承关系等准备工作
lookUpImpOrForward第二部分

进入了一个循环逻辑:
a.从本类的 method list 查找imp(查找的方式是getMethodNoSuper_nolock 一会分析);
b.从本类的父类的cache查找impcache_getImp 汇编写的)
c.从本类的父类的method list 查找imp
...继承链遍历...(父类->...->根父类)
d.若上面环节有任何一个环节查找到了imp,跳出循环,缓存方法到本类的cachelog_and_fill_cache);
e.直到查找到nil,指定imp为消息转发,跳出循环。

跳出循环后的逻辑:

最后的返回操作

如果找到了imp,就会把imp缓存到本类cache里(log_and_fill_cache):
(注意这里不管是本类还是本类的父类找到了imp,都会缓存到本类中去)

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
    cls->cache.insert(sel, imp, receiver); // 插入缓存
}

看看在类和父类继承链中查找imp是个什么样的查找方式的(getMethodNoSuper_nolock):

/***********************************************************************
 * getMethodNoSuper_nolock
 * fixme
 * Locking: runtimeLock must be read- or write-locked by the caller
 **********************************************************************/
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        //  getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

search_method_list_inline里找到了method_t就会返回出去了(search_method_list_inline):

ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->isExpectedSize();
    
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
            return m;
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name() == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}

这里就是findMethodInSortedMethodListfindMethodInUnsortedMethodList通过sel找到method_t的。这两个函数的区别就是:

  • findMethodInUnsortedMethodList查找无序的方法列表,通过for循环遍历一个个对比sel从而取出method_t
findMethodInUnsortedMethodList
  • findMethodInSortedMethodList查找有序的方法列表,通过二分查找对比sel取出method_t
findMethodInSortedMethodList

总结消息发送慢速查找imp(c/c++):
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
1.从本类的 method list (二分查找/遍历查找)查找imp
2.从本类的父类的cache查找imp(汇编)
3.从本类的父类的method list (二分查找/遍历查找)查找imp
...继承链遍历...(父类->...->根父类)里找cachemethod listimp
4.若上面环节有任何一个环节查找到了imp,跳出循环,缓存方法到本类的cache,并返回imp
5.直到查找到nil,指定imp为消息转发,跳出循环,执行动态方法解析resolveMethod_locked

六、动态方法解析resolveMethod_locked

lookUpImpOrForward函数里有动态方法解析的入口逻辑:

动态方法解析的入口

当本类和本类继承链下的cachemethod list都查找不到impimp被赋值成了_objc_msgForward_impcache但是它没有调用,会进入动态方法解析流程,并且只会执行一次。
resolveMethod_locked的源码声明:

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
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); // inst:类对象   cls: 元类
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

如果类的实例调用的是实例方法:

resolveInstanceMethod(inst, sel, cls);

如果是类对象调用的类方法:

// inst:类对象     cls: 元类
resolveClassMethod(inst, sel, cls); 
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
    resolveInstanceMethod(inst, sel, cls);
}
1.类的实例方法动态方法解析

案例:声明一个WJPerson类继承NSObject,给它声明一个实例方法 -(void)say; 不写say方法实现
分析方法调用:[[WJPerson alloc] say];

流程解析:
1.缓存查找,没有找到
2.类查找/父类查找
3.动态方法解析 resolveMethod_locked,再去找cls的元类的resolveInstacenMethodimp
4.如果还是没有实现,再去找根元类,直到找到 NSObject根元类resolveInstacenMethodimp
5.系统主动给类对象发送一条resolveInstacenMethod消息(找到cls的元类继承链中的resolveInstacenMethod并调用它,并缓存到本类的cache)(这是为了补救imp没有找到的错误)
6.接着还会往类对象cache通过sel查找一次resolveInstacenMethodimp

接下来看源码:
如果 cls类对象 不是元类,就会进入resolveInstanceMethod方法里:

/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    // lookUpImpOrNilTryCache 会去元类继承链里找是否有resolveInstacenMethod的imp,如果本类中没有实现它,最终找到NSObject的根元类的系统默认实现
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }
    // 给类对象主动发送resolveInstanceMethod消息(调用类对象里resolveInstacenMethod的imp,调用后会加入到类对象的cache里)
    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);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • a.分析lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
/** 
    inst:类对象
    sel:resolveInstanceMethod
    cls:元类
*/
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    // 元类里 cache里找imp,肯定没有找到
    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

会调用 lookUpImpOrForward 注意传递的参数。这个lookUpImpOrForward上面解析过了

流程:

  • 它会从元类的继承链里找resolveInstanceMethodimp
  • 如果本类里实现了+(Bool)resolveInstacenMethod方法,则在元类里能找到imp
  • 如果本类里没有实现它,则最终在根元类NSObject里找imp,因为NSObject类里默认声明了+(Bool)resolveInstacenMethod方法实现。
  • b.分析给类对象主动发送了resolveInstanceMethod的消息

sel+imp 绑定

流程:

  • 先到本类的cacheresolveInstanceMethodimp
  • 再到本类的元类继承链中找resolveInstanceMethodimp
  • 如果本类中没有实现 +(void)resolveInstanceMethod,则会找到NSObject默认的实现;
  • 调用这个imp,并缓存到本类的cache中去。
  • c.分析 IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

在a环节分析过,只是此时传递参数不一样了:

/** 
    inst:类的实例
    sel:resolveInstanceMethod
    cls:类对象
*/
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    // 类对象里找到imp,肯定是找到了,上面环节系统主动发送了一条消息
    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

可以看成是从cls类对象里的cacheimp,因为上面系统主动发送了一条消息了,所以类对象的cache肯定是缓存了有imp的,最后返回了imp出去。

2.类的类方法动态方法解析

案例:声明一个WJPerson类继承NSObject,给它声明一个实例方法 +(void)walk; 不写walk方法实现
分析方法调用:[WJPerson say];

流程解析:
1.缓存查找,没有找到
2.元类/根元类的方法列表找-(void)say,没有找到
3.动态方法解析resolveMethod_locked,再去找cls的元类的resolveClassMethodimp
4.如果还是没有实现,就去找元类->根元类直到找到NSObjec根元类resolveInstacenMethodimp
5.给元类发送一条消息(这里苹果做了处理,下面做源码分析),让sel+imp绑定

接下来看源码:

/***********************************************************************
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
/**
 * inst: 类对象
 * cls: 元类
 */
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    // lookUpImpOrNilTryCache 会去元类继承链里找是否有resolveClassMethod的imp,如果本类中没有实现它,最终找到NSObject的根元类的系统默认实现
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    // 处理元类,nonmeta赋值成类对象
    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);
        }
    }
    // 给类对象发送resolveClassMethod消息,让sel+imp绑定
    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);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • a.分析lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
/***********************************************************************
* lookUpImpOrForward / lookUpImpOrForwardTryCache / lookUpImpOrNilTryCache
* The standard IMP lookup.
*
* The TryCache variant attempts a fast-path lookup in the IMP Cache.
* Most callers should use lookUpImpOrForwardTryCache with LOOKUP_INITIALIZE
*
* Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails)
* With    LOOKUP_NIL: returns nil on negative cache hits
*
* inst is an instance of cls or a subclass thereof, or nil if none is known.
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use LOOKUP_NIL.
**********************************************************************/
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    // 元类的cache里找resolveClassMethod的imp,肯定是找不到的
    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);// inst: 类对象   cls: 元类
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

会调用 lookUpImpOrForward 注意传递的参数。这个lookUpImpOrForward上面解析过了

流程:

  • 会从元类的继承链里找resolveClassMethodimp
  • 如果本类里实现了+(Bool)resolveClassMethod方法,则在元类里能找到imp
  • 如果本类里没有实现它,则最终在根元类NSObject里找imp,因为NSObject类里默认声明了+(Bool)resolveClassMethod方法实现。
  • b.赋值nonmeta

看看getMaybeUnrealizedNonMetaClass做了什么事:

/***********************************************************************
* getMaybeUnrealizedNonMetaClass
* Return the ordinary class for this class or metaclass. 
* `inst` is an instance of `cls` or a subclass thereof, or nil. 
* Non-nil inst is faster.
* The result may be unrealized.
* Used by +initialize. 
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst)
{
    static int total, named, secondary, sharedcache, dyld3;
    runtimeLock.assertLocked();
    ASSERT(metacls->isRealized());

    total++;

    // return cls itself if it's already a non-meta class
    // 如果传递进来的metacls不是元类,则返回metacls,说明这时metacls是类对象
    // 然后我们类方法调用的是元类,继续往下走
    if (!metacls->isMetaClass()) return metacls;

    // metacls really is a metaclass
    // which means inst (if any) is a class

    // special case for root metaclass
    // where inst == inst->ISA() == metacls is possible
    // 元类的isa == metacls 说明metacls是根元类(只有根元类的isa指向自己)
    if (metacls->ISA() == metacls) {
        Class cls = metacls->getSuperclass(); // 取根元类的父类就是 NSObject
        ASSERT(cls->isRealized());
        ASSERT(!cls->isMetaClass());
        ASSERT(cls->ISA() == metacls); // NSObject的isa指向根元类
        if (cls->ISA() == metacls) return cls; // 返回NSObject的类对象,但我们使用[WJPerson walk]; 传递进来的metacls只是 WJPerson的元类,是有意地让代码往下走
    }

    // use inst if available
    // 如果类对象存在
    if (inst) {
        Class cls = remapClass((Class)inst); // 直接强转成类对象
        // cls may be a subclass - find the real class for metacls
        // fixme this probably stops working once Swift starts
        // reallocating classes if cls is unrealized.
        while (cls) {
            if (cls->ISA() == metacls) {
                ASSERT(!cls->isMetaClassMaybeUnrealized());
                return cls;
            }
            cls = cls->getSuperclass();
        }
#if DEBUG
        _objc_fatal("cls is not an instance of metacls");
#else
        // release build: be forgiving and fall through to slow lookups
#endif
    }

    // See if the metaclass has a pointer to its nonmetaclass.
    if (Class cls = metacls->bits.safe_ro()->getNonMetaclass())
        return cls;

    // try name lookup
    {
        Class cls = getClassExceptSomeSwift(metacls->mangledName());
        if (cls && cls->ISA() == metacls) {
            named++;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: %d/%d (%g%%) "
                             "successful by-name metaclass lookups",
                             named, total, named*100.0/total);
            }
            return cls;
        }
    }

    // try secondary table
    {
        Class cls = (Class)NXMapGet(nonMetaClasses(), metacls);
        if (cls) {
            secondary++;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: %d/%d (%g%%) "
                             "successful secondary metaclass lookups",
                             secondary, total, secondary*100.0/total);
            }

            ASSERT(cls->ISA() == metacls);            
            return cls;
        }
    }

    if (Class cls = getPreoptimizedClassesWithMetaClass(metacls)) {
        if (PrintInitializing) {
            if (objc::inSharedCache((uintptr_t)cls)) {
                sharedcache++;
                _objc_inform("INITIALIZE: %d/%d (%g%%) "
                             "successful shared cache metaclass lookups",
                             sharedcache, total, sharedcache*100.0/total);
            } else {
                dyld3++;
                _objc_inform("INITIALIZE: %d/%d (%g%%) "
                             "successful dyld closure metaclass lookups",
                             dyld3, total, dyld3*100.0/total);
            }
        }

        return cls;
    }

    _objc_fatal("no class for metaclass %p", (void*)metacls);
}

nonmeta赋值成一个类对象

  • c.分析给类对象主动发送了resolveClassMethod的消息

sel+imp 绑定

流程:

  • 先到本类的cacheresolveClassMethodimp
  • 再到本类的元类继承链中找resolveClassMethodimp
  • 如果本类中没有实现 +(void)resolveClassMethod,则会找到NSObject默认的实现;
  • 调用这个imp,并缓存到本类的cache中去。
  • d.分析 IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

可以看成是从cls类对象里的cacheimp,因为上面系统主动发送了一条消息了,所以类对象的cache肯定是缓存了有imp的,最后返回了imp出去。

  • e.再去查找resolveInstanceMethod

程序继续往下走:

这个判断是,如果我们开发者没有重写去实现这个+(Bool)resolveClassMethod:(SEL)sel,那就会进入这个if体里边:resolveInstanceMethod

这就跟上面的动态实例方法解析走一样的流程,但是注意这里的inst是类对象 cls是元类

接下来的操作就是,去元类 -> 根元类方法列表查找resolveInstanceMethodimp,最后在NSObject跟元类找到。
接着系统给元类发送一条消息resolveInstanceMethod,并且最终通过lookUpImpOrForwardTryCache使用sel去查找imp一次。

不管是调用实例方法还是类方法(都没有写方法实现的情况下),都可以通过在NSObject的分类里重写+(Bool)resolveInstanceMethod:(SEL)sel捕获到,动态指定imp

  • f.最后调用imp

如果在动态方法解析过程中动态给sel指定了imp(此时缓存已有了),则去调用它。

ps:
1.在我们重写resolveInstanceMethod/resolveClassMethod,在其方法内部打印,控制台会输出两次打印信息。这是为什么呢?

第一次调用resolveInstanceMethod是在resolveMethod_locked里通过objc_msgSend调用的:

第一次调用resolveInstanceMethod

第二次通过bt打印堆栈信息,发现是消息转发__forwarding__的慢速转发环节的方法签名methodSignatureForSelector -> class_getInstanceMethod -> lookupImpOrForward -> resolveInstanceMethod

第二次调用resolveInstanceMethod

2.resolveClassMethod的设计:
我们在调用类方法[WJTeacher say]的时候,say方法没有实现,并且没有重写resolveClassMethod方法,此时在NSObject分类里重写resolveInstanceMethod已经是可以的。因为上面已经分析过了。

设想一下如果苹果没有设计resolveClassMethod,那么就应该在元类里面给程序员去重写resolveInstanceMethod,但是元类苹果并没有提供出来,谁也没见过。于是乎resolveClassMethod设计出来的作用其实就是简化了resolveInstanceMethod的查找流程。

附上NSObject的动态方法解析的实现:

七、消息转发

cache没有找到imp,类的继承链里的方法列表都没有找到imp,并且resolveInstanceMethod / resolveClassMethod 返回NO就会进入消息转发。

我们在 lookUpImpOrForward 的时候就看到 imp 被指定成了_objc_msgForward_impcache
它就是消息转发的流程;又到了我们的源码汇编阶段:

_objc_msgForward_impcache
_objc_forward_handler

看到上面截图的报错吗,是不是和我们没有找到imp方法是崩溃报错是那么熟悉。

消息转发过程只有汇编在调用,消息转发是调用CoreFoundation的代码没有开源!看不到函数调用过程了。但是可以想办法让它开源。让它反汇编出来:

首先我们找到这个路径下的CoreFoundation文件,把它复制到桌面

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation

下载安装一个反编译软件 Hopper Disassembler v4,将CoreFoundation文件拖入反编译软件中得到反编译代码

接下来看两张图
第一张是我们Xcode方法调用时没有写方法实现,并且没有做动态方法解析成功的报错

Xcode方法调用报错

第二张是反编译软件的代码

点进去__forwarding_prep_0__

再点进去看__forwarding__到底做了什么事

__forwarding__

得到了汇编里的__forwarding__很长的一段代码,发现了forwardingTargetForSelector

如果类里没有实现forwardingTargetForSelector就会 goto loc_12ca57
之后就会进入慢速转发环节:

就会做僵尸对象判断,然后继续往下走,拿到方法签名sel: methodSignatureForSelector,如果响应了方法名称还会继续往下走,做一些方法签名的判断等等,接着

这就是消息转发流程的汇编的调用顺序。
所以我们消息转发可以进行如下处理:

#pragma mark - 动态消息转发流程 ---- 只有汇编进行调用
// 消息快速转发
+ (id)forwardingTargetForSelector: (SEL)aSelector {
    return [super forwardingTargetForSelector:aSelector];
}

// 消息慢速转发
+ (NSMethodSignature *)methodSignatureForSelector: (SEL) aSelector {
    if (aSelector == @selector(walk)) {
        return [NSMethodSignature signatureWithObjCTypes:@"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation: (NSInvocation *)anInvocation {
    NSString *str = @"我是xxx";
    anInvocation.target = [WjStudent class];
    [anInvocation setArgument:&str atIndex:2];
    anInvocation.selector = @selector(run:);
    [anInvocation invoke];
}
消息转发流程

八、动态方法解析/消息转发 的案例

1.实例方法的动态方法解析

WJPerson.h

#import 
@interface WJPerson : NSObject
@end

WJPerson.m

#import "WJPerson.h"
@implementation WJPerson
- (void)personTalk {
    NSLog(@"%s", __func__);
}
@end

WJTeacher.h

#import 
@interface WJTeacher : NSObject
- (void)talk;
@end

WJTeacher.m

#import "WJTeacher.h"
#import "WJPerson.h"
#import 
@implementation WJTeacher
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s -- %@", __func__, NSStringFromSelector(sel));
    if (sel == @selector(talk)) {
        IMP imp = class_getMethodImplementation(self, @selector(teacherTalk));
        // 绑定别的类的imp也是可以的
//        IMP imp = class_getMethodImplementation([WJPerson class], @selector(personTalk)); 
        class_addMethod(self.class, sel, imp, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)teacherTalk {
    NSLog(@"%s", __func__);
}
@end

main.m

#import 
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        WJTeacher *t = [WJTeacher alloc];
        [t talk];
    }
    return 0;
}
2.类方法的动态方法解析

WJTeacher.h

#import 
@interface WJTeacher : NSObject
+ (void)say;
@end

WJTeacher.m

#import "WJTeacher.h"
#import "WJPerson.h"
#import 
@implementation WJTeacher

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%s -- %@", __func__, NSStringFromSelector(sel));
    if (sel == @selector(say)) {
        IMP imp = class_getMethodImplementation(self, @selector(tracherSay));
        class_addMethod(objc_getMetaClass("WJTeacher"), sel, imp, "v@:"); // 元类的sel+imp绑定 = 类的类方法
        return YES;
    }
    return [super resolveClassMethod:sel];
}

- (void)tracherSay {
    NSLog(@"%s", __func__);
}
@end

main.m

#import 
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [WJTeacher say];
    }
    return 0;
}
3.消息快速转发:

WJPerson.h

#import 
@interface WJPerson : NSObject
@end

WJPerson.m

#import "WJPerson.h"
@implementation WJPerson
- (void)talk {
    NSLog(@"%s", __func__);
}
+(void)say {
    NSLog(@"%s", __func__);
}
@end

WJTeacher.h

#import 
@interface WJTeacher : NSObject
- (void)talk;
+ (void)say;
@end

WJTeacher.m

#import "WJTeacher.h"
#import "WJPerson.h"
#import 
@implementation WJTeacher
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s", __func__);
    if (aSelector == @selector(talk)) {
        return [WJPerson new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s", __func__);
    if (aSelector == @selector(say)) {
        return WJPerson.class;
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

main.m

#import 
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        WJTeacher *t = [WJTeacher alloc];
        [t talk];
        [WJTeacher say];
    }
    return 0;
}

4.消息慢速转发:

WJPerson.h

#import 
@interface WJPerson : NSObject
@end

WJPerson.m

#import "WJPerson.h"
@implementation WJPerson
- (void)talk {
    NSLog(@"%s", __func__);
}
@end

WJTeacher.h

#import 
@interface WJTeacher : NSObject
- (void)talk;
@end

WJTeacher.m

#import "WJTeacher.h"
#import "WJPerson.h"
#import 
@implementation WJTeacher

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

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s", __func__);
    WJPerson *p = [WJPerson alloc];
    if ([self respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self];
    } else if ([p respondsToSelector:anInvocation.selector]){
        [anInvocation invokeWithTarget:p];
    } else {
        NSLog(@"%@没有imp", NSStringFromSelector(anInvocation.selector));
    }
}
@end

main.m

#import 
#import "WJTeacher.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        WJTeacher *t = [WJTeacher alloc];
        [t talk];
    }
    return 0;
}

你可能感兴趣的:(iOS 消息发送、动态方法解析和消息转发 objc4-838.1源码)