objc_msgSend()
前面说过了,我们在写代码时候的会调用objc_msgSend系列的方法,然后再调用lookUpImpOrForward()方法,其实这只是一个笼统的说法。
当我们调用方法时候,编译起会根据实际情况调用以下方法中的一种
- objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
- objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, ...)
- objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
- objc_msgSendSuper_stret(id _Nullable self, SEL _Nonnull op, ...)
这些方法的定义在message.h文件中可以找到。这些方法的功能其实都是去寻找对象的方法,并去执行相应的方法。
其中objc_msgSend()和objc_msgSendSuper()的接收者不一样。objc_msgSend()第一个参数是一个id类型的,而objc_msgSendSuper()第一个参数是objc_super结构体。定义如下
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
可以看到objc_super结构体中包含了两个对象,一个是id类型的方法接受者receiver,一个是receiver的父类。
当我们调用对象的方法的时候,如[objectA test]的时候,会调用会objc_msgSend()方法,把 objectA 这个对象传给objc_msgSend()中的第一个参数self。
当我们调用父类的方法的时候,如[super test]的时候(关于super的定义,在另外一篇文章中有提及到),会调用会objc_msgSendSuper()方法,把super对象传给objc_msgSendSuper()方法。
objc_msgSend()和objc_msgSendSuper()区别在于,objc_msgSend()是从子类开始查找方法,而objc_msgSendSuper()是从父类开始查找方法。
另外,在有如下定义
#if !OBJC_OLD_DISPATCH_PROTOTYPES
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#else
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
其中OBJC_OLD_DISPATCH_PROTOTYPES的意思是在调用objc_msgSend()时,是否需要转换成合适的方法指针。如OBJC_OLD_DISPATCH_PROTOTYPES为YES,我们在调用objc_msgSend()就需要声明方法的返回值类型和参数类型,如
((int (*)(id, SEL, NSString *, BOOL)) objc_msgSend)((id)obj,@selector(viewWillAppear:),YES);
如果OBJC_OLD_DISPATCH_PROTOTYPES为NO,在调用时,我们可以用如下的方式去调用
objc_msgSend((id)obj,@selector(viewWillAppear:),YES);
OBJC_OLD_DISPATCH_PROTOTYPES的值可以通过Build Setting中去设置在 Enable Strict Checking of objc_msgSend Calls 设置。
在objc库中,objc_msgSend()的实现是不开源的,但是可以通过反编译去找到伪代码的实现。在网上找了一份(来源https://www.jianshu.com/p/df6629ec9a25)
id objc_msgSend(id receiver, SEL op, ...)
{
//1............................ 对象空值判断。
//如果传入的对象是nil则直接返回nil
if (receiver == nil)
return nil;
//2............................ 获取或者构造对象的isa数据。
void *isa = NULL;
//如果对象的地址最高位为0则表明是普通的OC对象,否则就是Tagged Pointer类型的对象
if ((receiver & 0x8000000000000000) == 0) {
struct objc_object *ocobj = (struct objc_object*) receiver;
isa = ocobj->isa;
}
else { //Tagged Pointer类型的对象中没有直接保存isa数据,所以需要特殊处理来查找对应的isa数据。
//如果对象地址的最高4位为0xF, 那么表示是一个用户自定义扩展的Tagged Pointer类型对象
if (((NSUInteger) receiver) >= 0xf000000000000000) {
//自定义扩展的Tagged Pointer类型对象中的52-59位保存的是一个全局扩展Tagged Pointer类数组的索引值。
int classidx = (receiver & 0xFF0000000000000) >> 52
isa = objc_debug_taggedpointer_ext_classes[classidx];
}
else {
//系统自带的Tagged Pointer类型对象中的60-63位保存的是一个全局Tagged Pointer类数组的索引值。
int classidx = ((NSUInteger) receiver) >> 60;
isa = objc_debug_taggedpointer_classes[classidx];
}
}
//因为内存地址对齐的原因和虚拟内存空间的约束原因,
//以及isa定义的原因需要将isa与上0xffffffff8才能得到对象所属的Class对象。
struct objc_class *cls = (struct objc_class *)(isa & 0xffffffff8);
//3............................ 遍历缓存哈希桶并查找缓存中的方法实现。
IMP imp = NULL;
//cmd与cache中的mask进行与计算得到哈希桶中的索引,来查找方法是否已经放入缓存cache哈希桶中。
int index = cls->cache.mask & op;
while (true) {
//如果缓存哈希桶中命中了对应的方法实现,则保存到imp中并退出循环。
if (cls->cache.buckets[index].key == op) {
imp = cls->cache.buckets[index].imp;
break;
}
//方法实现并没有被缓存,并且对应的桶的数据是空的就退出循环
if (cls->cache.buckets[index].key == NULL) {
break;
}
//如果哈希桶中对应的项已经被占用但是又不是要执行的方法,则通过开地址法来继续寻找缓存该方法的桶。
if (index == 0) {
index = cls->cache.mask; //从尾部寻找
}
else {
index--; //索引减1继续寻找。
}
} /*end while*/
//4............................ 执行方法实现或方法未命中缓存处理函数
if (imp != NULL)
return imp(receiver, op, ...); //这里的... 是指传递给objc_msgSend的OC方法中的参数。
else
return objc_msgSend_uncached(receiver, op, cls, ...);
}
/*
方法未命中缓存处理函数:objc_msgSend_uncached的C语言版本伪代码实现,这个函数也是用汇编语言编写。
*/
id objc_msgSend_uncached(id receiver, SEL op, struct objc_class *cls)
{
//这个函数很简单就是直接调用了_class_lookupMethodAndLoadCache3 来查找方法并缓存到struct objc_class中的cache中,最后再返回IMP类型。
IMP imp = _class_lookupMethodAndLoadCache3(receiver, op, cls);
return imp(receiver, op, ....);
}
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
可以看到,objc_msgSend()方法的执行流程是
1 判断对象是否为空
2 处理tagget pointer,找到相应的类,
3 判断类的缓存列表里面是否找到方法的实现,如果找到,则执行
4 如果缓存列表里面没找到方法实现,则调用lookUpImpOrForward查找方法
方法缓存
待补充
方法查找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();
// Optimistic cache lookup 去方法的缓存列表找方法
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;//如果找到方法,则跳转到最下方的done_nolock
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
//
runtimeLock.lock();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
// CFI attack 破坏机器码执行来控制程序行为的攻击
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
//
// TODO: this check is quite costly during process startup.
checkIsKnownClass(cls);//检查类的来源
if (slowpath(!cls->isRealized())) {//如果类没有标志为已实现,则需要判断锁是否释放掉了
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);//这个主要是判断是否解锁,如果锁释放掉,则重新枷锁
// runtimeLock may have been dropped but is now locked again
}
//如果类没有初始化,则初始化类
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
//#define fastpath(x) (__builtin_expect(bool(x), 1))
//#define slowpath(x) (__builtin_expect(bool(x), 0))
//__builtin_expect作用是"允许程序员将最有可能执行的分支告诉编译器"。这个指令的写法为:__builtin_expect(EXP, N)。意思是:EXP==N的概率很大。
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
//如果当前调用的方法是initialize方法,而且是由messenger(一般是我们自己调用)调用,会调用两次,但是第一次调用initialize就会把类标记为initialized
}
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookpu the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
//重新查找方法缓存
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
//查找当前类的方法列表
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == 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.查找父类的缓存,如果还是没有找到方法,则返回forward_imp的实现
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:
log_and_fill_cache(cls, imp, sel, inst, curClass);//缓存方法调用
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
这段代码的执行流程如图
lookUpImpOrForward执行流程.jpglookUpImpOrForward()执行完以后,把imp返回给objc_msgSend_uncached()方法。objc_msgSend_uncached()方法会去直接执行imp。
由于lookUpImpOrForward()中有可能返回的是方法的具体实现,有可能返回的是消息转发forward_imp。如果返回的是forward_imp,那么将进入消息转发流程。
消息转发流程发在下一篇文章讲述。
网友评论