消息发送的本质是objc_msgSend,至于为啥是这个,可以通过断点调试,这样就直接进入汇编,因为是汇编代码,熟悉常用指令即可,大部分根据注释走下去

objc_msgSend源码分析流程

这是一段汇编代码,直接搜索objc_msgSend,在arm64.s的文件中

  • Entry _objc_msgSend

  • 消息接收者的判断

  • GetClassFromIsa_p16 isa指针处理

  • CacheLookup(查找自身缓存)

  • 找到就CacheHit调用,cache_t处理bucket以及内存哈希处理

  • 如果没找到imp,就是调用MissLabelDynamic,这个传进来的__objc_msgSend_uncached

  • __objc_msgSend_uncached内部调用MethodTableLookup 方法表查找(接下来就是_lookUpImpOrForward 进入下一个流程)

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstantmov    x15, x16            // stash the original isa
LLookupStart\Function:// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRSldr p10, [x16, #CACHE]              // p10 = mask|bucketslsr   p11, p10, #48           // p11 = maskand   p10, p10, #0xffffffffffff   // p10 = bucketsand    w12, w1, w11            // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16ldr p11, [x16, #CACHE]          // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)tbnz    p11, #0, LLookupPreopt\Functionand  p10, p11, #0x0000ffffffffffff   // p10 = buckets
#elseand    p10, p11, #0x0000fffffffffffe   // p10 = bucketstbnz   p11, #0, LLookupPreopt\Function
#endifeor   p12, p1, p1, LSR #7and  p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
#elseand    p10, p11, #0x0000ffffffffffff   // p10 = bucketsand    p12, p1, p11, LSR #48       // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4ldr   p11, [x16, #CACHE]              // p11 = mask|bucketsand   p10, p11, #~0xf         // p10 = bucketsand    p11, p11, #0xf          // p11 = maskShiftmov  p12, #0xfffflsr p11, p12, p11           // p11 = mask = 0xffff >> p11and    p12, p1, p11            // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endifadd   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// 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_ADDRSadd p13, p10, w11, UXTW #(1+PTRSHIFT)// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))// p13 = buckets + (mask << 1+PTRSHIFT)// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4add   p13, p10, p11, LSL #(1+PTRSHIFT)// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endifadd   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 hitcmp  p9, #0              // } while (sel != 0 &&ccmp    p13, p12, #0, ne        //     bucket > first_probed)b.hi    4bLLookupEnd\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 = bucketsautdb  x10, x16            // auth as early as possible
#endif// x12 = (_cmd - first_shared_cache_sel)adrp x9, _MagicSelRef@PAGEldr   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_shiftlsr x17, x11, #55           // w17 = (hash_shift, ...)lsr  w9, w12, w17            // >>= shiftlsr  x17, x11, #60           // w17 = mask_bitsmov  x11, #0x7ffflsr 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_shiftlsr   x17, x11, #48           // w17 = (hash_shift, hash_mask)lsr    w9, w12, w17            // >>= shiftand  x9, x9, x11, LSR #53        // &=  mask
#endifldr   x17, [x10, x9, LSL #3]      // x17 == sel_offs | (imp_offs << 32)cmp    x12, w17, uxtw.if \Mode == GETIMPb.ne \MissLabelConstant      // cache misssub    x0, x16, x17, LSR #32       // imp = isa - imp_offsSignAsImp x0ret
.elseb.ne   5f              // cache misssub    x17, x16, x17, LSR #32      // imp = isa - imp_offs
.if \Mode == NORMALbr x17
.elseif \Mode == LOOKUPorr x16, x16, #3 // for instrumentation, note that we hit a constant cacheSignAsImp x17ret
.else
.abort  unhandled mode \Mode
.endif5:    ldursw  x9, [x10, #-8]          // offset -8 is the fallback offsetadd  x16, x16, x9            // compute the fallback isab    LLookupStart\Function       // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES.endmacro
ENTRY _objc_msgSendUNWIND _objc_msgSend, NoFramecmp  p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERSb.le LNilOrTagged        //  (MSB tagged pointer looks negative)
#elseb.eq   LReturnZero
#endifldr   p13, [x0]       // p13 = isaGetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:// calls imp or objc_msgSend_uncachedCacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:b.eq   LReturnZero     // nil checkGetTaggedClassb LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endifLReturnZero:// x0 is already zeromov  x1, #0movi  d0, #0movi  d1, #0movi  d2, #0movi  d3, #0retEND_ENTRY _objc_msgSend

_lookUpImpOrForward 慢速查找或者消息转发

慢速查找

直接上源码

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{const IMP forward_imp = (IMP)_objc_msgForward_impcache;IMP imp = nil;Class curClass;// 省略一些代码for (unsigned attempts = unreasonableClassCount();;) {if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
// 这里不会走,就是当前类没找到缓存才来到这
#if CONFIG_USE_PREOPT_CACHESimp = 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;}// 当最后父类为nil时,还没找到方法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_CACHESwhile (cls->cache.isConstantOptimizedCache(/* strict */true)) {cls = cls->cache.preoptFallbackClass();}
#endiflog_and_fill_cache(cls, imp, sel, inst, curClass);}done_unlock:runtimeLock.unlock();if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {return nil;}return imp;
}
  • getMethodNoSuper_nolock在这个方法内查找函数,从cls的data内根据sel匹配到对应的method_t,如果没找到,就会继续从父类中继续找cache_getImp,递归到这里,最后父类为nil时,imp赋值为forward_imp,下一个流程就会进入resolveMethod_locked,进行动态方法解析,就会如果找到了,就直接跳转done,如果还没有,就会_objc_msgForward_impcache,发送找不到消息

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{runtimeLock.assertLocked();ASSERT(cls->isRealized());// fixme nil cls? // fixme nil sel?// 从data内拿到方法列表auto const methods = cls->data()->methods();for (auto mlists = methods.beginLists(),end = methods.endLists();mlists != end;++mlists){// 根据sel查找到对应的method_tmethod_t *m = search_method_list_inline(*mlists, sel);if (m) return m;}return nil;
}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 listif (auto *m = findMethodInUnsortedMethodList(sel, mlist))return m;}return nil;
}ALWAYS_INLINE static method_t *
findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName)
{for (auto& meth : *list) {if (getName(meth) == sel) return &meth;}return nil;
}

done内会调用log_and_fill_cache来将imp加入到方法缓存,这里就是之前分享过的文章cache_t原理内的内容,一环套一环。

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

_objc_msgForward_impcache源码,会报经典错误,找不到方法。

    STATIC_ENTRY __objc_msgForward_impcache// No stret specialization.b  __objc_msgForwardEND_ENTRY __objc_msgForward_impcacheENTRY __objc_msgForwardadrp    x17, __objc_forward_handler@PAGEldr    p17, [x17, __objc_forward_handler@PAGEOFF]TailCallFunctionPointer x17END_ENTRY __objc_msgForward// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p ""(no message forward handler is installed)", class_isMetaClass(object_getClass(self)) ? '+' : '-', object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

动态方法解析

还是一样,接着之前的resolveMethod_locked,上源码,这里分为对象方法,和类方法,就拿对象方法为例。

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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {resolveInstanceMethod(inst, sel, cls);}}// chances are that calling the resolver have populated the cache// so attempt using itreturn lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

resolveInstanceMethod

  • 查找resolveInstanceMethod这个方法有没有实现,没有的话跳过到消息转发
  • 有实现的话,将resolved设置为yes,在这边可以动态加入一个IMP,会再进行一次查找
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{runtimeLock.assertUnlocked();ASSERT(cls->isRealized());SEL resolve_sel = @selector(resolveInstanceMethod:);if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {// Resolver not implemented.return;}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. clsIMP 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));}}
}

消息转发

这一块是在CoreFoundation内的,未开源,我们只能通过反汇编来解析。这是用模拟器,直接调用FFPerson的test方法(没有具体实现,只有申明),崩溃后,在栈中找的

新建一个FFStudent,在.m文件中实现test方法,同时在FFPerson中添加forwardingTargetForSelector,返回FFStudent对象,再运行就不会崩溃了。

- (id)forwardingTargetForSelector:(SEL)aSelector{return [FFStudent alloc];
}

注释forwardingTargetForSelector,再测试方法签名,添加代码为下面代码块,发现这样也可以不崩溃,但是方法就不会调用了,我们可以在forwardInvocation中转发对象来调用。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{NSLog(@"%s",__func__);if (aSelector == @selector(test)) {return [NSMethodSignature signatureWithObjCTypes:"v@:"];}return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@"%s",__func__);
}// 这里更改调用对象
- (void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@"%s",__func__);[anInvocation invokeWithTarget:[FFStudent alloc]];
}

总结

消息发送的本质是调用runtime的objc_msgSend,流程为

  • 判断消息接收者是否为空,为空的话,直接return,不会崩溃
  • 快速查找
    • 在自身方法列表或者缓存中查找
    • 查找到就调用,没找到就进入慢速查找
  • 慢速查找
    • 递归从父类的方法列表和缓存中查找,
    • 找到就调用,并加入缓存,没有进入动态方法决议resolveMethod_locked
  • 动态方法决议
    • 对象方法判断+ (BOOL)resolveInstanceMethod:(SEL)sel返回值,类方法判断+ (BOOL)resolveClassMethod:(SEL)sel返回值
    • 这里可以在返回前动态增加方法,这样返回yes的话,会再次查找方法来实现
    • 返回NO,进入消息转发
  • 消息转发
    • 判断forwardingTargetForSelector返回值,有返回对象,直接到返回对象调用方法,没有返回,进入下一步看看有没有方法签名

    • - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,没有返回对应的方法签名,则报错,有则进入下一步- (void)forwardInvocation:(NSInvocation *)anInvocation做进一步处理

    • - (void)forwardInvocation:(NSInvocation *)anInvocation这里可以更改消息接收者,方法签名,但是一定要上一步methodSignatureForSelector有返回,不然不会调用,也可以不实现,这样方法就不会调用,可以不崩溃

iOS进阶之底层原理-消息机制相关推荐

  1. iOS进阶之底层原理-应用程序加载(dyld加载流程、类与分类的加载)

    iOS应用程序的入口是main函数,那么main函数之前系统做了什么呢? 我们定义一个类方法load,打断点,查看栈进程,我们发现dyld做了很多事,接下来就来探究到底dyld做了什么. 什么是dyl ...

  2. iOS 进阶之底层原理一OC对象原理alloc做了什么

    人狠话不多,直接上干货.这是第一篇,之后还会持续更新,当作自己学习的笔记,也同时分享给大家,希望帮助更多人. 首先,我们来思考,下面这段代码的输出是否相同.答案很明显,p1.p2.p3是指向相同的对象 ...

  3. iOS进阶之底层原理-block本质、block的签名、__block、如何避免循环引用

    面试的时候,经常会问到block,学完本篇文章,搞通底层block的实现,那么都不是问题了. block的源码是在libclosure中. 我们带着问题来解析源码: blcok的本质是什么 block ...

  4. iOS进阶之底层原理-锁、synchronized

    锁主要分为两种,自旋锁和互斥锁. 自旋锁 线程反复检查锁变量是否可用,处于忙等状态.一旦获取了自旋锁,线程会一直保持该锁,直至释放,会阻塞线程,但是避免了线程上下文的调度开销,适合短时间的场合. 互斥 ...

  5. iOS进阶之底层原理-线程与进程、gcd

    线程与进程 线程的定义 线程是进程的基本单位,一个进程的所有任务都在线程中执行 进程要想执行任务,必须的有线程,进程至少要有一条线程 程序启动默认会开启一条线程,也就是我们的主线程 进程的定义 进程是 ...

  6. iOS进阶之底层原理-weak实现原理

    基本上每一个面试都会问weak的实现原理,还有循环引用时候用到weak,今天我们就来研究下weak的实现原理到底是什么. weak入口 我们在这里打个断点,然后进入汇编调试. 这里就很明显看到了入口, ...

  7. iOS进阶之底层原理-cache_t

    接着上一篇的对象结构探索,我们详细介绍cache_t.源码为最新的objc4-818.2. cache_t的底层结构 struct cache_t { // 省略一堆私有属性,方法 public:// ...

  8. iOS进阶之底层原理-isa与对象

    上一篇文章探究了对象的创建已经底层结构,这篇详细介绍isa.对象以及互相的关系. isa是什么 从源码分析,isa是个共用体,封装了类的信息. nonpointer:是否对isa指针开启指针优化,0为 ...

  9. iOS开发系列--通知与消息机制

    http://www.cocoachina.com/ios/20150318/11364.html 概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣 ...

最新文章

  1. Waymo自动驾驶汽车扎堆冲进死胡同,一天多达50辆,附近居民蚌埠住了
  2. http如何像tcp一样实时的收消息?
  3. java编程语言大全_JAVA编程语言的基础知识(一)
  4. Check Dependency 2(一个检查.net部署文件完整性及一致性工具)
  5. C语言在计算机专业的功能,C语言程序设计在高职院校计算机专业教学中重要作用.pdf...
  6. 面试 .NET 开发​,为什么也要考算法?​
  7. 给老板做PPT必备:文字加拼音
  8. 做骨龄检测_小柚熊:骨龄测试最佳年龄
  9. 机器学习十大经典算法之K-Means聚类算法
  10. 直播预告|阿里云天池牛年读书会《中学生可以这样学Python(微课版)》
  11. linux 双网卡bond命令,Linux的双网卡绑定(即bond0)
  12. vj p1038题解
  13. Nginx(二)状态信息(status)
  14. iOS /clang:-1: linker command failed with exit code 1 (use -v to see invocation) 报错
  15. 全网首发:ProGuard保持一个类名函数名需要加public
  16. android汉字转拼音
  17. centerOS环境变量配置
  18. captura 录制出来的是黑屏_黑屏:我录制的视频播放时画面是黑的解决方案 - Bandicam(班迪录屏)...
  19. MAC Photoshop标题栏不见了
  20. tampermonkey脚本php,Tampermonkey挂机脚本常用代码片段

热门文章

  1. (转) 服务接口统一返回的格式
  2. 腾讯云CMQ消息队列在Windows环境下的使用
  3. 解决mantis不能上传附件问题
  4. Java中Image类与ImageIcon类的区别
  5. 微信-js sdk invalid signature签名错误 问题解决
  6. iscroll动态加载数据完美解决方案
  7. Java继承Exception自定义异常类教程以及Javaweb中用Filter拦截并处理异常
  8. 基于webpack的前端工程化开发解决方案探索(一):动态生成HTML
  9. 一个WEB网站高并发量的解决方案
  10. 【转】使用Chrome Frame,彻底解决浏览器兼容问题