专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员小灰  ·  清华大学《DeepSeek学习手册》(全5册) ·  昨天  
OSC开源社区  ·  升级到Svelte ... ·  3 天前  
程序猿  ·  “我真的受够了Ubuntu!” ·  2 天前  
程序员的那些事  ·  李彦宏自曝开源真相:从骂“智商税”到送出“史 ... ·  4 天前  
51好读  ›  专栏  ›  SegmentFault思否

iOS 底层探索 - 消息查找

SegmentFault思否  · 公众号  · 程序员  · 2020-02-06 11:45

正文

本文转载于思否社区专栏:leejunhui's blog
作者: leejunhui




No.1

objc_msgSend 汇编补充



我们知道,之所以使用汇编来实现 objc_msgSend 有两个原因:

•  因为 C 无法通过写一个函数来保留未知的参数并且跳转到一个任意的函数指针。
objc_msgSend 必须足够快。

1.1 objc_msgSend 流程


ENTRY _objc_msgSend
•  对消息接收者进行判断、处理 (id self, sel _cmd)
•  taggedPointer 判断处理
GetClassFromIsa_p16 isa 指针处理拿到 class
CacheLookup 查找缓存
cache_t 处理 bucket 以及内存哈希处理
  • 找不到递归下一个 bucket

  • 找到了就返回 {imp, sel} = *bucket->imp\

  • 遇到意外就重试

  • 找不到就跳到 junpMiss

__objc_msgSend_uncached 找不到缓存 imp
STATIC ENTRY __objc_msgSend_uncached
MethodTableLookup 方法表查找
  • save parameters registers

  • self 以及 _cmd 准备

  • _class_lookupMethodAndLoadCache3 调用



No.2

通过汇编找到下一流程



我们在探索 objc_msgSend 的时候,当找不到缓存的时候,会来到一个地方叫做 objc_msgSend_uncached ,然后会来到 MethodTableLook up ,然后会有一个核心的查找方法 __class_lookupMethodAndLoadCache3 。但是我们知道其实已经要进入 C/C++ 的流程了,所以我们还可以汇编来定位。

我们打开 Always Show Disassembly 选项


然后我们进入 objc_msgSend 内部


然后我们进入 _objc_msgSend_uncached 的内部


我们会来到 _class_lookupMethodAndLoadCache3 ,这就是真正的方法查找实现。


No.3

代码分析方法查找流程



3.1 对象方法测试


对象的实例方法 - 自己有
对象的实例方法 - 自己没有 - 找父类的
对象的实例方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject
对象的实例方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 崩溃


3.2 类方法测试


类方法 - 自己有
类方法 - 自己没有 - 找父类的
类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject
类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 崩溃
类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 但是有对象方法



No.4

源码分析方法查找流程



我们直接定位到 _class_lookupMethodAndLoadCache3 源码处:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
接着我们进入 lookUpImpOrForward ,这里注意一下, cache 是传的 NO ,因为来到这里已经说明缓存不存在,所以需要进行方法查找。


4.1 lookUpImpOrForward


我们接着定位到 lookUpImpOrForward 的源码处:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
由该方法的参数我们可以知道, lookUpImpOrForward 应该是个公共方法, initialize cache 分别代表是否避免 +initialize 和是否从缓存中查找。
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
如果 cache YES ,那么就直接调用 cache_getImp 来从 cls 的缓存中获取 sel 对应的 IMP ,如果找到了就返回。
if (!cls->isRealized()) {
realizeClass(cls);
}
判断当前要查找的 cls 是否已经完成了准备工作,如果没有,则需要进行一下类的 realize

4.2 从当前类上查找


// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
上面的方法很显然,是从类的方法列表中查找 IMP 。这里加两个大括号的目的是形成局部作用域,让命名不会不想冲突。通过 getMethodNoSuper_nolock 查找 Method ,找到了之后就调用 log_and_fill_cache 进行缓存的填充,然后返回 imp

4.2.1 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;
}

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;
}
getMethodNoSuper_nolock 实现很简单,就是从 cls data() 中进行遍历,然后对遍历到的 method_list_t 结构体指针再次调用 search_method_list sel 进行匹配。这里的 findMethodInSortedMethodList 我们再接着往下探索。

4.2.2 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;

for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);

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--;
}
return (method_t *)probe;
}

if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}

return nil;
}
findMethodInSortedMethodList 的核心逻辑是二分查找,这种算法的前提是有序的集合。

4.3 从父类中查找


源码如下:
// 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 = 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) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
•  在父类中查找的时候,和在当前类查找有一点不同的是需要检查缓存。
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;
}
•  如果在父类中找到了 IMP ,同时判断是否是消息转发的入口,如果不是消息转发,那么就把找到的 IMP 通过 log_and_fill_cache 缓存到当前类的缓存中;如果是消息转发,就退出循环。
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
•  如果父类缓存中没有找到,那么就查找父类的方法列表,这里和上面在当前类中的方法列表中查找是异曲同工之妙,就不再赘述了。

4.4 方法解析

// 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;
}
如果在类和父类中都没有找到,Runtime 给了我们一个机会来进行动态方法解析。
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]

_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
我们来分析一下 _class_resolveMethod :
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]

_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
•  判断当前类是否是元类,如果不是的话,调用:
_class_resolveInstanceMethod
•  如果是元类的话,说明要查找的是类方法,调用:
_class_resolveClassMethod

4.4.1 _class_resolveInstanceMethod


首先我们分析动态解析对象方法:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
这里还有一个注意点:
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
对当前 cls 发送 SEL_resolveInstanceMethod 消息,如果返回的是 YES ,那说明当前类是实现了动态方法解析。

由上面的代码可知动态方法解析到最后会回到 lookUpImpOrForward 。注意这里的传参:

cach e YES resolver NO ,什么意思呢?

Cache the result (good or bad) so the resolver doesn't fire next time.
缓存查找的结果,所以解析器下一次就不会被触发,其实本质上就是打破递归。

4.4.2 _class_resolveClassMethod


我们接着分析动态解析类方法:
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());

if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);

// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found"






请到「今天看啥」查看全文