美文网首页
第七篇:objc_msgSend方法深入剖析

第七篇:objc_msgSend方法深入剖析

作者: 坚持才会看到希望 | 来源:发表于2022-05-05 17:52 被阅读0次
@interface HPWPerson : NSObject

- (void)study;
- (void)happy;
+ (void)eat;

@end

@implementation HPWPerson

- (void)study {
    NSLog(@"%s",__func__);
}
- (void)happy {
    NSLog(@"%s",__func__);
}
+ (void)eat {
    NSLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        HPWPerson *p = [HPWPerson alloc];
        [p study];
        [p happy];
        
    }
    return NSApplicationMain(argc, argv);
}

通过上面写的demo,我们先把对应的工程里main.c文件进行一个clang编译,这样会生成一个main.cpp的文件,这个我们可以分析下源码。第一个参数是:消息的接受者。 第二个参数是:消息的方法名。如果我们方法里带有参数的话,就还会生成一个介绍方法参数

        HPWPerson *p = ((HPWPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HPWPerson"), sel_registerName("alloc"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("study:"), (NSString *)&__NSConstantStringImpl__var_folders_64_v4jdthx95753k1gbfyy30w0w0000gn_T_main_64f0fb_mi_3);
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("happy"));

当我们在代码中写 ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("happy"))的时候其和 [p happy]是同样的一个效果。但是需要注意的是需要导入一个#import <objc/message.h>。

接着我们来看下runtime里的objc_msgSendSuper方法

-(instancetype)init {
   if (self = [super init]) {
       NSLog(@"%@",[self class]);
       NSLog(@"%@",[super class]);
   }
   return self;
}

- (void)study {
   
[super study];
   
}
 HPWTeacher *t = [[HPWTeacher alloc] init];
   [t study];

同样我们把上面的工程进行clang编译main.c生成main.app,看下对应的源码,通过源码的分析后,我们知道objc_msgSendSuper是从父类开始进行方法的查找的,objc_msgSend是通过当前的类进行查找的。 [super study]这个代码我们也可以通过objc_msgSendSuper来写出来,如下图所示:在这里设置了父类class,它会在父类方法开始查找。

  struct objc_super lg_objc_super;
   lg_objc_super.receiver = self;
   lg_objc_super.super_class = HPWPerson.class;

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

大家想一想,如果我们把改成 lg_objc_super.super_class = HPWTeacher.class会发生什么呢,会出现死循环,这个是因为study这个方法是在HPWTeacher这个类里,所以会一直重复调用。如果改成 lg_objc_super.super_class = NSObject.class会发生什么呢,会发生闪退,这个是因为NSObject里没有study这个方法。

接着我们再探究下objc_msgSend这个方法

其实 objc_msgSend苹果是用汇编写的,其目的是运行快。下面是其源码:

//进入objc_msgSend流程
   ENTRY _objc_msgSend
//流程开始,无需frame
   UNWIND _objc_msgSend, NoFrame

//判断p0(消息接受者)是否存在,不存在则重新开始执行objc_msgSend
   cmp p0, #0          // nil check and tagged pointer check

//如果支持小对象类型。返回小对象或空
#if SUPPORT_TAGGED_POINTERS
//b是进行跳转,b.le是小于判断,也就是小于的时候LNilOrTagged
   b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
//等于,如果不支持小对象,就LReturnZero
   b.eq    LReturnZero
#endif
//通过p13取isa
   ldr p13, [x0]       // p13 = isa
//通过isa取class并保存到p16寄存器中
   GetClassFromIsa_p16 p13, 1, x0  // p16 = class
//LGetIsaDone是一个入口
LGetIsaDone:
   // calls imp or objc_msgSend_uncached
//进入到缓存查找或者没有缓存查找方法的流程
   CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// nil check判空处理,直接退出
   b.eq    LReturnZero     // nil check
   GetTaggedClass
   b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

总结 objc_msgSend的流程:
objc_msgSend(receiver,sel...)
1.判断receiver是否存在
2.receiver存在的话通过isa找class
3.拿到class后进行一个内存平移,通过内存平移找到cache
4.找到cache之后,找到buckets
5.在buckets里对比sel方法
6.buckets里有对应的sel就调用cacheHit命中,然后调用imp

  1. buckets没有对应的sel就会调用_objc_msgSend_uncached

上面这些都是方法的快速查找流程。


43DD07F4-8243-4F93-977E-EA55710C2E10.png

下面这个源码是通过二分法进行查找,也就是慢速查找。

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
   ASSERT(list);

   auto first = list->begin();
   auto base = first;
   decltype(first) probe;

   uintptr_t keyValue = (uintptr_t)key;
   uint32_t count;
   
   for (count = list->count; count != 0; count >>= 1) {
       probe = base + (count >> 1);
       
       uintptr_t probeValue = (uintptr_t)getName(probe);
       
       if (keyValue == probeValue) {
           // `probe` is a match.
           // Rewind looking for the *first* occurrence of this value.
           // This is required for correct category overrides.
           while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
               probe--;
           }
           return &*probe;
       }
       
       if (keyValue > probeValue) {
           base = probe + 1;
           count--;
       }
   }
   
   return nil;
}

下面这个方法是通过慢速查找后存储在cache里,如果子类调用父类,那么方法会存储到子类的cache里。

IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel)
{
   IMP imp;

   // fixme this is incomplete - no resolver, +initialize - 
   // but it's only used for .cxx_construct/destruct so we don't care
   ASSERT(sel == SEL_cxx_construct  ||  sel == SEL_cxx_destruct);

   // Search cache first.
   //
   // If the cache used for the lookup is preoptimized,
   // we ask for `_objc_msgForward_impcache` to be returned on cache misses,
   // so that there's no TOCTOU race between using `isConstantOptimizedCache`
   // and calling cache_getImp() when not under the runtime lock.
   //
   // For dynamic caches, a miss will return `nil`
   imp = cache_getImp(cls, sel, _objc_msgForward_impcache);

   if (slowpath(imp == nil)) {
       // Cache miss. Search method list.

       mutex_locker_t lock(runtimeLock);

       if (auto meth = getMethodNoSuper_nolock(cls, sel)) {
           // Hit in method list. Cache it.
           imp = meth->imp(false);
       } else {
           imp = _objc_msgForward_impcache;
       }

       // Note, because we do not hold the runtime lock above
       // isConstantOptimizedCache might flip, so we need to double check
       if (!cls->cache.isConstantOptimizedCache(true /* strict */)) {
           cls->cache.insert(sel, imp, nil);
       }
   }

   return imp;
}
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;
}

慢速查找总结:
首先调用lookUpImpOrForward方法 -- 先找当前类的methondlist,如果没有找到就会找父类的cache,如果没找到就到父类的methondlist里去找,一直到父类为nil,NSObject是万类之主。如果还是没有找到的话会进行一个forwordImp的环节,也就是消息转发。

所有的总结:
通过上面的方法查找,我们发现并没有实例方法和类方法的区分,也就更加说明了我们之前说的一个点。也就是元类的设计,是为了复用消息机制,提高查找效率,不然都放在类对象里就会有要判断是类方法还是实例方法之分。

相关文章

网友评论

      本文标题:第七篇:objc_msgSend方法深入剖析

      本文链接:https://www.haomeiwen.com/subject/enunyrtx.html