通过前几章的探索,已经了解了对象和类的底层实现,对属性、成员变量和方法的存储也有了必定的了解,明白了方法的缓存机制,那么方法究竟是如何进行调用的,它的整个流程是什么样的,怎么进行转发的,咱们本章来探究一下编程
传送门☞iOS底层学习 - 类的前世此生(一)数组
传送门☞iOS底层学习 - 类的前世此生(二)缓存
咱们都知道OC是一门动态语言,分为编译时和运行时,而Runtime是OC进行运行时支持APIsass
一、Objective-C code:例如@selector()bash
二、NSObject的方法:例如NSSelectorFromString()编程语言
三、Runtime Api:例如sel_registerNameide
将代码装载在内存在须要的是进行调用就叫作运行时函数
Xcode中command+B就是编译时操做,将语法翻译成机器能识别的语言,编译成的可执行文件,运行的时候就是将这个可执行文件加载到内存中 post
经过运行clang命令,查看方法sayNB
在编译后是如何运行的性能
LGPerson *person = [LGPerson alloc];
[person sayNB];
复制代码
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
复制代码
经过上面的例子咱们知道,方法再底层会变成objc_msgSend
方法,即经过objc_msgSend
来发送消息,其实obj
表示消息接受者,sel_registerName
是Runtime的API,表示根据一个方法名
,返回一个SEL
objc_msgSend(obj, sel_registerName("sayNB"));
复制代码
SEL sel_registerName(const char *name) {
return __sel_registerName(name, 1, 1); // YES lock, YES copy
}
复制代码
既然知道了方法的本质即为objc_msgSend
的消息发送,那么他是怎么实现的呢,经过objc源码,咱们能够知道objc_msgSend
方法再底层是由汇编来实现的,咱们主要研究iOS设备的arm64结构的汇编实现
使用汇编的主要缘由:
objc_msgSend
相关汇编实现以下
GetClassFromIsa_p16
方法即表明经过对象的
isa & mask
便可获得类,这个和以前章节讲过的方式是同样的,只不过这里是汇编实现
快速流程即经过底层汇编代码快速找到方法的调用IMP,经过上面的代码分析,咱们能够查看CacheLookup NORMAL
相关代码
经过注释咱们可知,该方法有3中参数NORMAL
,GETIMP
,LOOKUP
,咱们目前使用的是NORMAL
参数
CacheLookup NORMAL
主要查找流程以下,基本就是经过汇编来实现上一章节中,对类的cache_find
的操做,从而找到对应缓存的IMP
CacheHit
即为把响应的
IMP
返回给接受者
CheckMiss
说明类的缓存中没有响应的IMP,会调用
__objc_msgSend_uncached
进行下一步查找
__objc_msgSend_uncached
的实现中,主要进行了
MethodTableLookup
操做
MethodTableLookup
中主要对未知的参数进行了一系列的处理,而后,调用
__class_lookupMethodAndLoadCache3
进行
慢速查找,这是一个C和C++函数,因此调用起来比汇编要慢
1.对接受者进行判空处理
2.进行taggedPoin
t等异常处理
3.获取到接受者isa
,对isa & mask
获取到class
4.经过对class的isa进行指针偏移,获取到cache_t
5.经过对cache_t中key & mask
获取到下标,查找到对应的bucket
,获取到其中的IMP
6.若是上述没有找到IMP,走到__objc_msgSend_uncached
中的MethodTableLookup
开始慢速查找
经过上述的汇编快速查找,若是没有方法的缓存,则会进入这个慢速查找的流程,那么慢速查找流程的起点是什么,咱们能够经过打断点,看汇编代码查看
首先断点在须要跟踪的方法,打开Xcode中的Always Show Disassembly
便可跟踪到相对应的汇编实现
__objc_msgSend_uncached
_class_lookupMethodAndLoadCache3
,自此开始了慢速查找的流程
lookUpImpOrForward
方法分析IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
💡//️接收传入的参数, initialize = YES , cache = NO , resolver = YES
💡//初始化相关参数
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
💡// 缓存查找, 由于cache传入的为NO,这里不会进行缓存查找,由于在汇编语言中CacheLookup已经查找过
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
❗️// 当类没有初始化时,初始化类和父类、元类等,保证后面方法的查找流程
runtimeLock.read();
if (!cls->isRealized()) {
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
retry:
runtimeLock.assertReading();
💡// 防止动态添加方法,缓存会变化,再次查找缓存。imp = cache_getImp(cls, sel);
💡// 若是找到imp方法地址, 直接调用done, 返回方法地址
if (imp) goto done;
❗️// 查找方法列表, 传入类对象和方法名
{
💡// 根据sel去类对象里面查找方法
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
💡// 若是方法存在,则缓存方法,
💡// 内部调用的就是 cache_fill。
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
💡// 方法缓存以后, 取出函数地址imp并返回
imp = meth->imp;
goto done;
}
}
❗️// 若是类方法列表中没有找到, 则去父类的缓存中或方法列表中查找方法
{
unsigned attempts = unreasonableClassCount();
❗️// 若是父类缓存列表及方法列表均找不到方法,则去父类的父类去查找。一层层进行递归查找,直到找到NSObject类
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.");
}
❗️// 查找父类的cache_t缓存
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
💡// 在父类中找到方法, 在本类中缓存方法, 注意这里传入的是cls, 将方法缓存在本类缓存列表中, 而非父类中
log_and_fill_cache(cls, imp, sel, inst, curClass);
// 执行done, 返回imp
goto done;
}
else {
// 跳出循环, 中止搜索
break;
}
}
❗️// 查找父类的方法列表
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
💡// 一样拿到方法, 在本类进行缓存
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
// 执行done, 返回imp
goto done;
}
}
}
❗️// ---------------- 消息发送阶段完成,没有找到方法实现,进入动态解析阶段 ---------------------
❗️//首先检查是否已经被标记为动态方法解析,若是没有才会进入动态方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
💡 //将triedResolver标记为YES,下次就不会再进入动态方法解析
triedResolver = YES;
goto retry;
}
❗️// ---------------- 动态解析阶段完成,进入消息转发阶段 ---------------------
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
💡// 返回方法地址
return imp;
}
复制代码
getMethodNoSuper_nolock方法
就是一个简单的一个遍历方法列表
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
复制代码
search_method_list
表示使用二份查找寻找方法
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
💡// 若是方法列表已经排序好了,则经过二分查找法查找方法,以节省时间
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
💡//若是方法列表没有排序好就遍历查找
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
复制代码
findMethodInSortedMethodList
二分查找方法具体实现
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
❗️// >>1 表示将变量n的各个二进制位顺序右移1位,最高位补二进制0。
❗️// count >>= 1 若是count为偶数则值变为(count / 2)。若是count为奇数则值变为(count-1) / 2
for (count = list->count; count != 0; count >>= 1) {
❗️// probe 指向数组中间的值
probe = base + (count >> 1);
💡// 取出中间method_t的name,也就是SEL
uintptr_t probeValue = (uintptr_t)probe->name;
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)probe[-1].name) {
probe--;
}
// 取出 probe
return (method_t *)probe;
}
💡// 若是keyValue > probeValue 则折半向后查询
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
复制代码
_objc_msgForward_impcache
简述在经过消息查找和动态解析失败后,最后会走到_objc_msgForward_impcache
方法,调用__objc_msgForward
,最终调用__objc_forward_handler
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
复制代码
搜索__objc_forward_handler
发现这是一个C++方法,最终实现以下,这就是最终找不到方法时,LLDB打印输出的内容
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);
}
复制代码
1.首先对须要的变量进行初始化操做和加锁操做
2.其次若是没有进行初始化,则初始化类,父类、元类等,保证方法的查找
3.在receive的方法列表中进行二分查找,若是找到,则返回,并写入缓存
4.若是没找到,则一层层递归receive的父类的缓存和方法列表,直到NSObject,找到即返回,并写入receive的缓存
5.若是没找到,则进入动态方法解析
流程,进行动态方法的解析,有则执行
6.若是没有动态方法解析,则进入消息转发
流程
7.若是上述都没有实现和处理,则最终没法找到方法,会崩溃
OC的消息机制能够分为一下三个阶段:
经过本章,咱们基本了解了方法的本质和方法的查找流程,可是对于消息动态解析和消息转发的流程并无深刻了解,下一章节会着重讲解这两个部分