网站登录按钮怎么做,灰色行业做网站推广,饿了么网站做要多少钱,电子商务目前就业形势【iOS】—— 消息传递和消息转发 1. 消息传递SEL选择子IMP快速查找汇编代码查找过程总结消息转送快速查找IMP 慢速查找总结消息传递慢速查找IMP 2. 消息转发动态决议动态解析添加方法 快速转发慢速转发 总结动态决议消息转发消息的三次拯救 1. 消息传递
在iOS中#xff0c;消… 【iOS】—— 消息传递和消息转发 1. 消息传递SEL选择子IMP快速查找汇编代码查找过程总结消息转送快速查找IMP 慢速查找总结消息传递慢速查找IMP 2. 消息转发动态决议动态解析添加方法 快速转发慢速转发 总结动态决议消息转发消息的三次拯救 1. 消息传递
在iOS中消息传递机制是基于Objective-C语言的动态性质的一种编程方式。这个概念主要涉及两个概念发送者消息发送的对象和接受者消息接收的对象。当调用一个对象的方法的时候实际上是向这个对象发送了一条消息。
比如下面的代码
id returnValue [someObject messageName: parameter];someObject叫做接收者(receiver)messageName:叫做选择子(selector)选择子和参数合起来称为“消息”。
编译器看到此消息后将其转换为一条标准的C语言函数调用所调用的函数乃是消息传递机制中的核心函数叫做objc_msgSend
编译器看到上述这条消息会转换成一条标准的 C 语言函数调用 id returnValue objc_msgSend(someObject, selector(messageName:), parameter);objc_msgSend函数这个函数将消息接收者和方法名作为主要参数其原型如下所示 // 不带参数
objc_msgSend(receiver, selector)
// 带参数
objc_msgSend(receiver, selector, arg1, arg2,...)
objc_msgSend通过以下几个步骤实现了动态绑定机制
首先获取selector指向的方法实现。因为相同的方法可能会在不同的类中有不同的实现所以要根据receiver来进行判断。其次传递对象方法指定的参数来调用方法实现。最后返回方法实现的返回值。当消息传递到一个对象的时候首先从运行时的系统缓存objc_cache中进行查找。如果找到就执行。否则执行下一步。objc_msgSend通过对象的isa指针获取类的结构体然后在结构体的methodLists中查找方法如果没有找到就沿着superclass找到父类在父类的分发表methodLists中继续查找。以此类推一直沿着继承链找到NSObject类。一旦找到selector传入相应的参数来实现具体方法并将该方法加入到objc_cache。如果最后还没有找到就会进入消息转发流程。
SEL选择子
SEL 是选择器(Selector)的别名它是表示一个方法的符号名。选择器是用来表示一个方法名的可以看作是一个指向方法的指针。
在OC中方法并不是一个单纯的函数由两部分组成选择器SEL和实现体IMP。
选择器是一个字符串用来表示方法名字实现体是一个函数指针指向方法的实现。
每个方法在 Objective-C 运行时环境中都有一个选择器与之对应。选择器可以看作是一个内部的名称用于在运行时识别要被调用的方法。你可以通过 selector() 来获取一个方法的选择器。
例如假设你有一个名为 doSomething 的方法你可以这样获取它的选择器 SEL selector selector(doSomething);选择器主要用于以下几个方面
方法的调用可以通过 -performSelector: 方法和一些变体来间接调用一个方法。这在你需要在运行时动态决定要调用的方法时非常有用。作为方法的参数在很多 Cocoa 和 Cocoa Touch 的 API 中你会发现有许多方法的参数是选择器例如 NSTimer 的 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:。响应者链在 iOS 的事件处理和图形用户界面编程中选择器常常被用来确定哪个方法应该被调用来响应一个特定的事件例如按钮点击等。
选择器是在编译阶段由编译器生成的。编译器会根据方法名包括参数序列生成一个唯一的 ID这个 ID 就是 SEL 类型的。
其中需要注意的是selector等于是把方法名翻译成SEL方法名。其仅仅关心方法名和参数个数并不关心返回值与参数类型
IMP
**IMP是一个函数指针,保存了方法地址。**它是OC方法实现代码块的地址通过他可以直接访问任意一个方法。免去发送消息的代码IMP声明: typedef id (IMP)(id,SEL,...);IMP 是一个函数指针这个被指向的函数包含一个接收消息的对象id(self 指针)调用方法的选标SEL方法名以及不定个数的方法参数并返回一个id。
IMP和SEL的区别与联系
SEL类方法的指针相当于一种编号区别与IMP。IMP函数指针保存了方法的地址。
SEL是通过表取对应关系的IMP进行方法的调用。可以将SEL想象成一个指向方法名的指针但它并不直接关联方法的实现代码而是作为查找方法实现即IMP的一个标记或键值。
查找 IMP方式大致分为两种快速查找和慢速查找。
快速查找
汇编代码查找过程 首先从cmp p0,#0开始这里p0是寄存器存放的是消息接受者。当进入消息发送入口时先判断消息接收者是否存在不存在则重新执行objc_msgSend。 b.le LNilOrTaggedb是跳转到的意思。le是如果p0小于等于0总体意思是若p0小于等于0则跳转到LNilOrTagged执行b.eq LReturnZero直接退出这个函数。 //进入objc_msgSend流程ENTRY _objc_msgSend//流程开始无需frameUNWIND _objc_msgSend, NoFrame//判断p0消息接收者是否存在不存在则重新开始执行objc_msgSendcmp p0, #0 // nil check and tagged pointer check
//如果支持小对象类型返回小对象或空
#if SUPPORT_TAGGED_POINTERS//b是进行跳转b.le是小于判断也就是p0小于0的时候跳转到LNilOrTaggedb.le LNilOrTagged // (MSB tagged pointer looks negative)
#else//等于如果不支持小对象就跳转至LReturnZero退出b.eq LReturnZero
#endif//通过p13取isaldr p13, [x0] // p13 isa//通过isa取class并保存到p16寄存器中GetClassFromIsa_p16 p13, 1, x0 // p16 class
如果消息接受者不为nil,汇编继续跑到CacheLookup NORMAL在cache中查找imp来看一下具体的实现 //在cache中通过sel查找imp的核心流程
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant//// Restart protocol://// As soon as were 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 were 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就是cacheldr p10, [x16, #CACHE] // p10 mask|bucketslsr p11, p10, #48 // p11 maskand p10, p10, #0xffffffffffff // p10 bucketsand 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\Functionand p10, p11, #0x0000ffffffffffff // p10 buckets
#else//and表示与运算将与上mask后的buckets值保存到p10寄存器and p10, p11, #0x0000fffffffffffe // p10 buckets//p11与#0比较如果p11不存在就走Function如果存在走LLookupPreopttbnz p11, #0, LLookupPreopt\Function
#endif//按位右移7个单位存到p12里面p0是对象p1是_cmdeor p12, p1, p1, LSR #7and p12, p12, p11, LSR #48 // x12 (_cmd ^ (_cmd 7)) mask
#elseand p10, p11, #0x0000ffffffffffff // p10 buckets//LSR表示逻辑向右偏移//p11, LSR #48表示cache偏移48位拿到前16位也就是得到mask//这个是哈希算法p12存储的就是搜索下标哈希地址//整句表示_cmd mask并保存到p12and 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.
#endif//去除掩码后bucket的内存平移//PTRSHIFT经全局搜索发现是3//LSL #(1PTRSHIFT)表示逻辑左移4位也就是*16//通过bucket的首地址进行左平移下标的16倍数并与p12相与得到bucket并存入到p13中add p13, p10, p12, LSL #(1PTRSHIFT)// p13 buckets ((_cmd mask) (1PTRSHIFT))// do {
//ldp表示出栈取出bucket中的imp和sel分别存放到p17和p9
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} *bucket--//cmp表示比较对比p9和p1如果相同就找到了对应的方法返回对应imp走CacheHitcmp p9, p1 // if (sel ! _cmd) {//b.ne表示如果不相同则跳转到3fb.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来判断是否是第一个bucketcmp 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 #(1PTRSHIFT)// p13 buckets (mask 1PTRSHIFT)
#elif CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16add p13, p10, p11, LSR #(48 - (1PTRSHIFT))// p13 buckets (mask 1PTRSHIFT)// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4add p13, p10, p11, LSL #(1PTRSHIFT)// p13 buckets (mask 1PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endifadd p12, p10, p12, LSL #(1PTRSHIFT)// 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, _MagicSelRefPAGEldr p9, [x9, _MagicSelRefPAGEOFF]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
#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_offscmp x12, x17, LSR #38.if \Mode GETIMPb.ne \MissLabelConstant // cache misssbfiz x17, x17, #2, #38 // imp_offs combined_imp_and_sel[0..37] 2sub x0, x16, x17 // imp isa - imp_offsSignAsImp x0ret
.elseb.ne 5f // cache misssbfiz x17, x17, #2, #38 // imp_offs combined_imp_and_sel[0..37] 2sub x17, x16, x17 // 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类对象/元类通过内存平移获得cache获得buckets。
在缓存中找到了就直接调用找到sel就会进入CacheHit去return or call imp返回或调用方法的实现(imp)。
如果没有找到缓存查找下一个bucket一直循环直到找到对应的方法最后还没有找到的话就调用objc_msgSend_uncached方法。
下面是上述判断跳转代码 //LGetIsaDone是一个入口
LGetIsaDone:
// calls imp or objc_msgSend_uncached//进入到缓存查找或者没有缓存查找方法的流程
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
__objc_msgSend_uncached源码汇编 STATIC_ENTRY __objc_msgSend_uncachedUNWIND __objc_msgSend_uncached, FrameWithNoSaves// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band p15 is the class to searchMethodTableLookupTailCallFunctionPointer x17END_ENTRY __objc_msgSend_uncached
其中调用了MethodTableLookup宏: 从方法列表中去查找方法 看一下它的结构 .macro MethodTableLookupSAVE_REGS MSGSEND// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)// receiver and selector already in x0 and x1mov x2, x16mov x3, #3bl _lookUpImpOrForward// IMP in x0mov x17, x0RESTORE_REGS MSGSEND.endmacro总结消息转送快速查找IMP
objc_msgSend(receiver, sel, …)
检查接受者是否存在为nil则不做任何处理通过receiverdeisa指针找到对应的class类对象找到类对象之后通过内存平移找到cache从cache中获取buckets从buckets中对比sel查看是否有同名方法如果有对应的sel就会进入到cacheHit调用imp如果没有对应的sel进入objc_msgSend_uncached然后到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())) {...省略部分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;}if (slowpath((curClass curClass-getSuperclass()) nil)) {// No implementation found, and method resolver didnt 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 dont 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;}}// 未找到实现。请尝试一次方法解析器。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;
}
检查类是否被初始化是否是个已知的关系确定继承关系的准备工作。 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_t *meth getMethodNoSuper_nolock(curClass, sel);if (meth) {imp meth-imp(false);goto done;}// 每次判断都会把curClass的父类赋值给curClassif (slowpath((curClass curClass-getSuperclass()) nil)) {// 没有找到实现方法解析器没有帮助。// 使用转发。imp forward_imp;break;}}// 如果超类链中存在循环则停止。if (slowpath(--attempts 0)) {_objc_fatal(Memory corruption in class list.);}// 超类缓存。imp cache_getImp(curClass, sel);if (slowpath(imp forward_imp)) {// 在超类中找到forward::条目。// 停止搜索但不要缓存;调用方法// 首先为这个类解析器。break;}if (fastpath(imp)) {// 在超类中找到方法。在这个类中缓存它。goto done;}}进入循环逻辑
从本类的methodList查找IMP查找的方式是getMethodNoSuper_nolock)从本类的父类的的cache中查找cache_getImp从本类的父类demethodList查找IMP…继承链遍历…父类-…-根父类若上面任何一个环节查找到了imp跳出循环缓存方法到本类的cache中直到查找到nil指定imp为消息转发跳出循环。
跳出循环后的逻辑 done:if (fastpath((behavior LOOKUP_NOCACHE) 0)) {
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下while (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;如果找到了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_LOGGINGif (slowpath(objcMsgLogEnabled implementer)) {bool cacheIt logMessageSend(implementer-isMetaClass(), cls-nameForLogging(),implementer-nameForLogging(), sel);if (!cacheIt) return;}
#endifcls-cache.insert(sel, imp, receiver); // 插入缓存
}getMethodNoSuper_nolock查找方式 tatic 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){// rdar://problem/46904873 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 listif (auto *m findMethodInUnsortedMethodList(sel, mlist))return m;}#if DEBUG// sanity-check negative resultsif (mlist-isFixedUp()) {for (auto meth : *mlist) {if (meth.name() sel) {_objc_fatal(linear search worked when binary search did not);}}}
#endifreturn nil;
}这里就是使用findMethodInSortedMethodList和findMethodInUnsortedMethodList通过sel找到method_t的。这两个函数的区别就是 前者是排好序的后者是未排好序的前者方法中的查询方式是二分查找后者则是普通查找。
总结消息传递慢速查找IMP
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
从本类的 method list (二分查找/遍历查找)查找imp从本类的父类的cache查找imp(汇编)从本类的父类的method list (二分查找/遍历查找)查找imp …继承链遍历…父类-…-根父类里找cache和method list的imp若上面环节有任何一个环节查找到了imp跳出循环缓存方法到本类的cache并返回imp直到查找到nil指定imp为消息转发跳出循环执行动态方法解析resolveMethod_locked
2. 消息转发
当一个对象无法接收某一消息时就会启动所谓“消息转发message forwarding”机制。通过消息转发机制我们可以告诉对象如何处理未知的消息。
消息转发机制大致可以分为三个步骤
动态方法解析备援接受者完整消息转发
下图为消息转发过程的示意图
动态决议
// No implementation found. Try method resolver once.
//未找到实现。尝试一次方法解析器
if (slowpath(behavior LOOKUP_RESOLVER)) {behavior ^ LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);
}
通过之前的源码可以发现如果没有找到方法则尝试调用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)
{lockdebug::assert_locked(runtimeLock);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);
}
主要用的的方法如下 // 类方法未找到时调起可以在此添加方法实现(BOOL)resolveClassMethod:(SEL)sel;
// 对象方法未找到时调起可以在此添加方法实现(BOOL)resolveInstanceMethod:(SEL)sel;
//其中参数sel为未处理的方法
上述代码大致流程
先判断进行解析的是不是元类如果不是元类则调用则调用resolveInstanceMethod进行对象方法动态解析如果是元类则调用resolveClassMethod进行类方法动态解析完成类方法动态解析后再次查询cls中的imp如果没有找到则进行一次对象方法动态解析。
而这两个方法resolveInstanceMethod和resolveClassMethod则称为方法的动态决议。 执行完上述代码后返回lookUpImpOrForwardTryCache IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{return _lookUpImpTryCache(inst, sel, cls, behavior);
}这个方法调用的是_lookUpImpTryCache方法 ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{lockdebug::assert_unlocked(runtimeLock);if (slowpath(!cls-isInitialized())) {// see comment in lookUpImpOrForwardreturn lookUpImpOrForward(inst, sel, cls, behavior);}IMP imp cache_getImp(cls, sel);if (imp ! NULL) goto done;
#if CONFIG_USE_PREOPT_CACHESif (fastpath(cls-cache.isConstantOptimizedCache(/* strict */true))) {imp cache_getImp(cls-cache.preoptFallbackClass(), sel);}
#endifif (slowpath(imp NULL)) {return lookUpImpOrForward(inst, sel, cls, behavior);}done:if ((behavior LOOKUP_NIL) imp (IMP)_objc_msgForward_impcache) {return nil;}return imp;
}进入_lookUpImpTryCache源码可以看到这里有cache_getImp也就是说在进行一次动态决议之后还会通过cache_getImp从cache里找一遍方法的sel。
#endif
if (slowpath(imp NULL)) {return lookUpImpOrForward(inst, sel, cls, behavior);
}
如果还是没找到(imp NULL)也就是无法通过动态添加方法的话还会执行一次lookUpImpOrForward这时候进lookUpImpOrForward方法这里behavior传的值会发生变化。
第二次进入lookUpImpOrForward方法后执行到if (slowpath(behavior LOOKUP_RESOLVER))这个判断时 // 这里就是消息转发机制第一层的入口if (slowpath(behavior LOOKUP_RESOLVER)) {behavior ^ LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}
根据变化后的behavior值和LOOKUP_RESOLVER值之间的关系导致该if语句内部只能进入第一次因此这个判断相当于单例。解释了为什么开头说的该动态解析resolveMethod_locked为什么只执行一次。
动态解析添加方法
在动态决议阶段可以为类添加方法以保证程序正常运行
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) cls : 给哪个类对象添加方法
name SEL类型给哪个方法名添加方法实现
imp : IMP类型的要把哪个方法实现添加给给定的方法名
types 就是表示返回值和参数类型的字符串我们来看一个例子
// Dog.h
#import Foundation/Foundation.hNS_ASSUME_NONNULL_BEGINinterface Dog : NSObject
- (void)print;
endNS_ASSUME_NONNULL_END
//Dog.m
#import Dog.h
#import objc/runtime.h
#import NiuBiDog.h
implementation Dogend可以看到print方法并未实现所以在主函数中调用程序一定会崩溃 然后我们将代码修改为下面这样: 在.m文件中增加这个方法 (BOOL)resolveInstanceMethod:(SEL)sel {NSLog(%s, sel %, __func__, NSStringFromSelector(sel));return [super resolveInstanceMethod:sel];
}程序依然会崩溃我们看看输出结果 是因为找不到imp而崩溃那么我们可以在这个方法里通过runtime的class_addMethod给sel动态的生成imp。其中第四个参数是返回值类型用void用字符串描述“v:” (BOOL)resolveInstanceMethod:(SEL)sel {NSLog(%s, sel %, __func__, NSStringFromSelector(sel));if (sel selector(print)) {IMP imp class_getMethodImplementation(self, selector(add));class_addMethod(self, sel, imp, v);return YES;}return [super resolveInstanceMethod:sel];
}- (void)add {NSLog(12345);
}快速转发
当cache没有找到imp类的继承链里的方法列表都没有找到imp并且resolveInstanceMethod / resolveClassMethod 返回NO就会进入消息转发。也就是所以如果本类没有能力去处理这个消息那么就转发给其他的类让其他类去处理。 done:if ((behavior LOOKUP_NIL) imp (IMP)_objc_msgForward_impcache) {return nil;}return imp;
从imp (IMP)_objc_msgForward_impcache进入消息转发机制。 查看一下这个方法 竟然是汇编实现的这就又印证了汇编速度更快的结论 STATIC_ENTRY __objc_msgForward_impcache// No stret specialization.b __objc_msgForwardEND_ENTRY __objc_msgForward_impcacheENTRY __objc_msgForwardadrp x17, __objc_forward_handlerPAGEldr p17, [x17, __objc_forward_handlerPAGEOFF]TailCallFunctionPointer x17END_ENTRY __objc_msgForwardDog类中定义print方法但是不实现利用forwardingTargetForSelector:(SEL)aSelector 方法进行消息快速转发。NiuBiDog类中定义print方法且实现
//NiuBiDog.h
#import Dog.hNS_ASSUME_NONNULL_BEGINinterface NiuBiDog : Dog
- (void)print;
endNS_ASSUME_NONNULL_END//NiuBiDog.m#import NiuBiDog.himplementation NiuBiDog- (void)print {NSLog(\n%s, __func__);
}end//Dog.m
#import Dog.h
#import objc/runtime.h
#import NiuBiDog.h
implementation Dog
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector selector(print)) {return [NiuBiDog new];}return [super forwardingTargetForSelector:aSelector];
}
end转发的作用在于如果当前对象无法响应消息就将它转发给能响应的对象。
慢速转发
如果消息的快速转发也没有找到方法后面还有个methodSignatureForSelector方法作用是方法有效性签名。 将刚才使用快速转发forwardingTargetForSelector方法注释后添加上methodSignatureForSelector方法后这个方法需要搭配forwardInvocation
forwardInvocation方法提供了一个入参类型是NSInvocation它提供了target和selector用于指定目标里查找方法实现。 // 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {NSLog(%s aSelector %, __func__, NSStringFromSelector(aSelector));return [NSMethodSignature signatureWithObjCTypes:v];
}- (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog( %s, 甘文崔, __func__);
}总结
防止系统崩溃的三个救命稻草动态解析、快速转发、慢速转发。 OC方法调用的本质就是消息发送消息发送是SEL-IMP的查找过程。
动态决议
// 类方法未找到时调起可以在此添加方法实现(BOOL)resolveClassMethod:(SEL)sel;
// 对象方法未找到时调起可以在此添加方法实现(BOOL)resolveInstanceMethod:(SEL)sel;
//其中参数sel为未处理的方法
消息转发
消息快速转发 - (id)forwardingTargetForSelector:(SEL)aSelector;
消息慢速转发 // 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 正向调用
- (void)forwardInvocation:(NSInvocation *)anInvocation;
消息的三次拯救
动态方法解析备援接收者完整消息转发