1. 前言
前面的文章了解了OC对象的本质、类的本质以及方法缓存的原理,那么这篇文章将来分析一下OC方法底层的原理。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
2. 方法的本质
作为一个iOS开发者,每个人多多少少都会对方法的本质有所了解,最简单的理解就是发送消息,毫无疑问,确实是这样的,那么是怎么发送的,接受者是谁,消息又是什么,都有什么参数呢?
下面看一组代码:
@interface GYMPerson : NSObject
- (void)playGame;
+ (void)loveLife;
@end
@interface GYMDeveloper : GYMPerson
- (void)writeCode;
+ (void)loveJob;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// GYMDeveloper类继承了GYMPerson类
GYMDeveloper *developer = [GYMDeveloper alloc];
[developer writeCode]; // 对象调用本类实例方法
[developer playGame]; // 对象调用父类实例方法
[GYMDeveloper loveJob]; // 子类调用本类类方法
[GYMDeveloper loveLife]; // 子类调用父类类方法
}
return 0;
}
当用clang将main.m转成main.cpp时,则main函数如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
GYMDeveloper *developer = ((GYMDeveloper *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GYMDeveloper"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)developer, sel_registerName("writeCode"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)developer, sel_registerName("playGame"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GYMDeveloper"), sel_registerName("loveJob"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GYMDeveloper"), sel_registerName("loveLife"));
}
return 0;
}
是不是看起来有些乱?我们整理一下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
GYMDeveloper *developer = (GYMDeveloper *)objc_msgSend(objc_getClass("GYMDeveloper"), sel_registerName("alloc"));
objc_msgSend(developer, sel_registerName("writeCode"));
objc_msgSend(developer, sel_registerName("playGame"));
objc_msgSend(objc_getClass("GYMDeveloper"), sel_registerName("loveJob"));
objc_msgSend(objc_getClass("GYMDeveloper"), sel_registerName("loveLife"));
}
return 0;
}
代码精简后,可以看到不管是创建对象,还是对象调用方法以及类调用类方法,都涉及到了一个重要的函数objc_msgSend,其中有两个重要的参数,一个是消息的接受者,一个是消息名称。
所以OC底层方法的实现就是发送消息。
3. 方法的查找流程
方法的查找流程分为快速查找流程和慢速查找流程。
3.1 快速查找流程
快速查找流程由汇编语言编写,从_objc_msgSend开始:
- 通过消息接受者的isa,查找其类或者元类(实例方法通过isa找对象的类,类方法通过isa找类的元类)。
- 当找到类或者元类后,通过内存偏移,找到类的cache属性,然后调用CacheLookUp。
- 在CacheLookUp流程中,将方法编号转换成对应的key,再去cache缓存中找该方法。
- 如果该方法已经在cache中缓存了,那么直接返回方法的imp。
- 如果在cache中找不到,那么进入JumpMiss流程,调用__objc_msgSend_uncached。
- 在__objc_msgSend_uncached中,调用MethodTableLookup,到此则意味着快速查找流程结束了。
- MethodTableLookup中,则准备一些相关的参数等,然后调用__class_lookupMethodAndLoadCache3进入慢速查找流程。
3.2 慢速查找流程
在上面进入__class_lookupMethodAndLoadCache3时,即意味着开始了慢速查找流程,此时将从汇编模式转成C代码模式,其对应的C函数为:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
在这个函数里面又调用了lookUpImpOrForward函数,具体说明请看注释:
/**
这个函数主要分为四部分:
1. 查找缓存中是否缓存了该方法,这个方法的cache有可能在别的地方调用的时候传true。
2. 判断当前的类是否合法,是否已经实例化,如果没有实例化,那么需要对类,类的父类等,以及类元类,元类的元类进行实例化。
3. 在当前类中的方法列表中查找该方法,如果查找到直接返回IMP。
4. 在父类的方法列表中查找该方法,如果父类没有,则循环找父类的父类,直到老祖宗级别,找到返回IMP。
5. 到这步如果还没有找到,那么进入方法转发阶段,这部分后面再讲。
6. 如果方法转发成功,万事大吉,否则崩溃报错。
*/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
// 在缓存中查找方法的IMP,如果有直接返回IMP。
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
//如果当前类没有实例化,那么调用realizeClass进行实例化。
if (!cls->isRealized()) {
/**
该函数中主要对类的ro、rw进行赋值,以及该类继承链上的父类,元类以及元类的元类等的rw、ro赋值等操作。
此操作主要确保后续方法查找过程中不会出现异常。
*/
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
// 在缓存中查找方法的IMP,如果有直接返回IMP。
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
// 在当前类的方法列表中查找该方法
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 如果顺利找到了方法,那么调用log_and_fill_cache进行方法缓存,此方法最终会调用上一篇文章(cache_t分析)中的方法缓存方法cache_fill_nolock
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
/**
如果能走到这里,说明在当前类中肯定没有找到该方法。
下面将遍历继承链上的所有父类,在父类中查找该方法。
*/
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 在父类的缓存中查找该方法,如果有先缓存下来,并将IMP返回。
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
// 如果缓存中没有,则在父类的方法列表中查找该方法。
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 如果找到了,在父类缓存该方法,并返回IMP。
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 如果执行到了这里,那么说明本类和继承链上的所有父类都没有该方法,那么则进行一次消息转发。
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
// 如果消息转发都失败了,那么会执行到这里,程序崩溃报错了。
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
当imp为_objc_msgForward_impcache的时候,则会走入汇编流程,最终调用下面的方法发出报错信息:
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
3.3 方法查找小结
实例方法的查找流程,如下图:

类方法的查找流程,如下图:

4. 结束语
本篇文章主要介绍了方法的本质,以及方法的查找流程。
方法的本质即是给调用方法的对象发送消息,并带上方法编号这个参数,然后进行方法查找。
方法查找分为快速查找和慢速查找。快速查找则通过汇编代码在cache中进行查找,当快速查找无结果的时候,进入慢速查找,遍历当前类或者元类的方法列表进行查找。
那么如果两种查找都找不到该怎么办呢?直接崩溃吗?
当然不是了,底层代码还给我们留了个转发的机制,即消息转发。
下一篇文章将介绍方法的消息转发,欢迎来阅读噢!
如果路过的朋友觉得这篇文章有所帮助,还望给个赞哦!\
原文作者:Daniel_Coder
原文地址:https://blog.csdn.net/guoyongming925/article/details/109061149
网友评论