@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
- buckets没有对应的sel就会调用_objc_msgSend_uncached
上面这些都是方法的快速查找流程。

下面这个源码是通过二分法进行查找,也就是慢速查找。
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的环节,也就是消息转发。
所有的总结:
通过上面的方法查找,我们发现并没有实例方法和类方法的区分,也就更加说明了我们之前说的一个点。也就是元类的设计,是为了复用消息机制,提高查找效率,不然都放在类对象里就会有要判断是类方法还是实例方法之分。
网友评论