12-objc_msgSend底层调用流程探究

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 12-objc_msgSend底层调用流程探究

初探

首先看下面这一段代码,声明一个Person类,并调用Person类中的test方法:

@interface Person : NSObject
- (void)test;
@end
@implementation Person
- (void)test{}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        [person test];
    }
    return 0;
}

使用以下命令转换成c++看一下底层实现:

xcrun -sdk iphoneos clang -arch arm64 -framework Foundation.framework -rewrite-objc main.m -o main.cpp


通过main.cpp文件,我们可以看到,test方法的调用转换成为以下函数的调用:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));
    }
    return 0;
}

可以看到底层是通过objc_msgSend给person对象发送了一个test消息,也是OC调用方法的消息机制;

objc_msgSend的使用

  • 导入头文件:

         
  • 设置Enable Strict Checking of objc_msgSend Calls为NO,避免编译报错,也可支持多个参数:

以下两种调用方法的方式等价:

[person test]; //等价
objc_msgSend(person, @selector(test));

objc_msgSend源码解读


_objc_msgSend

其中objc_msgSend的源码是通过汇编实现的,直接找到源码中的objc-msg-arm64.s文件:

//入口函数
  ENTRY _objc_msgSend
  UNWIND _objc_msgSend, NoFrame
    //po为消息的为objc_msgSend的第一个参数receiver
  cmp p0, #0      // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    //p0小于等于0会跳转到LNilOrTagged标签
  b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
    //等于0直接return
  b.eq LReturnZero
#endif
    //x0为self,把isa赋值给p13
  ldr p13, [x0] // p13 = isa
    //执行GetClassFromIsa_p16函数,参数为(isa,1,self)
  GetClassFromIsa_p16 p13, 1, x0 // p16 = class
//到这里就已经通过isa已经获取到了class
LGetIsaDone:
  // 查找缓存
  CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
  b.eq LReturnZero // nil check
  GetTaggedClass
  b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
先判断消息接收者是否为空,然后根据isa查找class,其中如果isa为0则直接返回,如果小于0,标记为LNilOrTagged,否则的话利用isa找到class,接下来去查找缓存,调用CacheLookup;CacheLookup
CacheLookup传递进去的参数NORMAL, _objc_msgSend, __objc_msgSend_uncached:
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    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, #CACHE] // p10 = mask|buckets
    lsr p11, p10, #48 // p11 = mask
    and p10, p10, #0xffffffffffff // p10 = buckets
    and w12, w1, w11 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
    tbnz p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
    and p10, p11, #0x0000fffffffffffe // p10 = buckets
    tbnz p11, #0, LLookupPreopt\Function
#endif
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    and p10, p11, #0x0000ffffffffffff // p10 = buckets
    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
    add    p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
                        // do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
    cmp p9, p1 // if (sel != _cmd) {
    b.ne 3f // scan more
                        // } else {
2: CacheHit \Mode // hit: call or return imp
                        // }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
    cmp p13, p10 // } while (bucket >= buckets)
    b.hs 1b
#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

主要就是用来查找缓存,其中CacheHit标签来确定是否命中缓存,如果查找到缓存,直接返回imp(函数地址),如果没有命中缓存会调用__objc_msgSend_uncached;

__objc_msgSend_uncached

STATIC_ENTRY __objc_msgSend_uncached
  UNWIND __objc_msgSend_uncached, FrameWithNoSaves
  // THIS IS NOT A CALLABLE C FUNCTION
  // Out-of-band p15 is the class to search
  MethodTableLookup
  TailCallFunctionPointer x17
  END_ENTRY __objc_msgSend_uncache

内部会调用MethodTableLookup函数;

MethodTableLookup

.macro MethodTableLookup
  SAVE_REGS MSGSEND
  // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
  // receiver and selector already in x0 and x1
  mov x2, x16
  mov x3, #3
  bl _lookUpImpOrForward
  // IMP in x0
  mov x17, x0
  RESTORE_REGS MSGSEND
.endmacro

内部又会调用_lookUpImpOrForward,进入c、c++代码逻辑;

lookUpImpOrForward

直接找到lookUpImpOrForward函数:

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())) {
        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.
            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;
}

内部又经过一系列的调用,主要做了以下几件事:

利用二分查找的方式,先从类对象里面查找方法,如果没有找到,会接着递归先从父类的缓存里面去找,如果没有找到,然后再向父类里面找,依次类推,最后如果找到,调用fill_cache插入到消息接收者类的缓存中去,并返回imp,如果还是没有找到,会进入动态消息解析,如果动态消息仍未实现,则imp是指向_objc_msgForward_impcache的汇编入口;

动态方法解析

实例方法调用resolveInstanceMethod,类方法调用resolveClassMethod:

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

动态方法解析使用实例

如果我们调用方法的时候,没有对方法进行实现,在动态方法解析中,我们有机会向类中动态添加方法,可以查看以下示例:

#import <objc/message.h>
@interface Person : NSObject
- (void)test;
@end
@implementation Person
- (void)addTest {
    NSLog(@"Person - addTest");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(addTest));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        [person test];
    }
    return 0;
}

打印结果:

Person - addTest

如果test没有实现,又没有在动态方法解析中添加方法,接下来会进入消息转发阶段,下篇分析,如果消息转发依旧没有实现,会报以下错误:

-[Person test]: unrecognized selector sent to instance 0x100717230

总结

objc_msgSend的执行流程?

  • 消息发送

  • 动态方法解析

  • 消息转发

objc_msgSend消息发送流程?

  • 首先判断receiver是否为空,如果为空直接return;
  • 如果不为空,先从消息接收者的类对象cache中查找,如果找到了,返回imp,则直接调用该方法;
  • 如果从cache中没有找到,则会继续从消息接收者本身的class_rw_t中查找,如果找到了则返回方法的imp,直接调用,并将方法缓存到消息接收者的cache中;
  • 如果没有找到则会从其superClass的cache中查找,如果找到了,返回imp,直接调用该方法,并将方法缓存到消息接收者的cache中;
  • 如果没找到,则继续从superClass的class_rw_t中查找,如果找到了,返回imp,直接调用该方法,并将方法缓存到消息接收者的cache中;
  • 如果没有找到,则继续递归向其父类中查找;
  • 如果到最后依旧没有找到,则会进行下一步骤,进行动态方法解析;



objc_msgSend动态方法解析

  • 首先判断是否动态解析过,如果有动态解析过,则直接进入下一步消息转发;
  • 如果没有动态解析过,则会根据实例方法或类方法调用对应的+resolveInstanceMethod:或+resolveClassMethod:来进行动态的添加方法,动态解析过后会重新执行从消息接收者的类对象cache中查找流程,如果动态添加了方法,在查找流程中,会将其插入到cache中;

本文部分内容来可能来源于网络,发布的内容如果侵犯了您的权益,请联系我们尽快删除!

相关文章
|
7月前
|
程序员 编译器 C++
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用(一)
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用
208 0
|
4月前
|
Android开发 iOS开发
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
52 0
|
4月前
|
存储 前端开发 JavaScript
PixiJS源码分析系列:第四章 响应 Pointer 交互事件(上篇)
PixiJS源码分析系列:第四章 响应 Pointer 交互事件(上篇)
|
7月前
|
算法 IDE 程序员
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用(三)
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用
142 5
|
7月前
|
设计模式 开发框架 编译器
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用(二)
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用
261 0
|
7月前
|
Java 程序员 API
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
619 0
|
安全 前端开发 API
【反序列化利用链通用思路】OkayCMS&Smarty反序列化利用链详细分析
OkayCMS 是一个来自俄罗斯现成的在线商店平台,提供了在真正的演示在线商店中工作的机会,可以在其中执行任何操作。在管理面板中无限制地添加和删除产品、导入、更改语言和货币。
147 0
|
NoSQL 应用服务中间件 Linux
Epoll的本质(内部实现原理)
Epoll的本质(内部实现原理)
211 0
Epoll的本质(内部实现原理)
|
存储 算法 iOS开发