初探
首先看下面这一段代码,声明一个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中;
本文部分内容来可能来源于网络,发布的内容如果侵犯了您的权益,请联系我们尽快删除!