前言
相关文章
相关代码:
objc4_752源码
上篇文章讲述了方法的本质实际就是调用objc_msgSend,然后经过汇编代码进行快速调用查找,或者根据C代码慢速查找;
汇编代码快速查找,已经在源码中做了详细的注释,并且在其中加了一个objc_msgSend反汇编伪代码,可以配合汇编代码一起查看,大家可以自行查看;
方法在类中的查找流程
首先我们来看一下代码来回顾一下之前的东西:查看Demo
int main(int argc, const char * argv[]) {
@autoreleasepool {
#pragma clang diagnostic push
// // 让编译器忽略错误,不然调用没有的方法,编译器编译完语法分析会报错,导致不能运行
#pragma clang diagnostic ignored "-Wundeclared-selector"
XZTeacher *teacher = [[XZTeacher alloc] init];
// 对象方法
// 自己有 - 返回自己
[teacher sayHello];
// 自己没有 - 老爸 -
[teacher sayNB];
// // 自己没有 - 老爸没有 - NSObject
[teacher sayMaster];
// 自己没有 - 老爸没有 - NSObject 没有
// unrecognized selector sent to instance 0x101806f30 调用下面会直接报错
// [teacher performSelector:@selector(saySomething)];
// 类方法
// 自己有 - 返回自己
[XZTeacher sayObjc];
// // 自己没有 - 老爸 -
[XZTeacher sayHappay];
// // 自己没有 - 老爸没有 - NSObject
[XZTeacher sayEasy];
// 自己没有 - 老爸没有 - NSObject 没有
// unrecognized selector sent to instance 0x100001368
// [XZTeacher performSelector:@selector(sayLove)];
// sayMaster]这个是NSObject中的实例方法
[XZTeacher performSelector:@selector(sayMaster)];
#pragma clang diagnostic pop
}
return 0;
}
我们来看一下这个方法的打印结果:
我们可以看到,我们使用[XZTeacher performSelector:@selector(sayMaster)],调用NSObject中的实例方法,也调用成功了;这个实际上,我们在之前的文章中也有提到过什么原因,这里再来回顾一下,首先类方法实际就是在元类中以实例方法的方式进行存储的,而元类的根源类,也是继承于NSObject的,所以在方法查找的过程中,类方法会最终查找到NSObject类的实例方法,找到并执行了;但是还有一部分方法是在本类,以及父类都找不到的时候就会崩溃,下面我们就进行分析一下
就是根据上图的查找结果,最终会找到sayMaster并执行
这个我们可以进行总结一下:
对象的实例方法:
-
自己有
-
自己没有-->父类有
-
自己没有-->父类没有-->父类的父类----直到NSObject
-
自己没有-->父类没有-->直到NSObject也没有--->崩溃
对象的类方法:
-
自己有
-
自己没有-->父类有
-
自己没有-->父类没有-->父类的父类----直到NSObject
-
自己没有-->父类没有-->直到NSObject类方法没有-->NSObject实例方法
-
自己没有-->父类没有-->直到NSObject类方法没有-->NSObject实例方法没有-->崩溃
方法的慢速查找流程
得到上面的结论之后,我们从底层源码进行分析,首先我们调用方法同过objc_msgSend 查找cachet-->进入汇编快速查找-->最终进入代码_class_lookupMethodAndLoadCache3 慢速查找,由于都是新方法,所以我们可以直接进入方法最后进行查看
我们直接查看_class_lookupMethodAndLoadCache3方法源码
//这里的extern 标示外部也可以调用这个方法
extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class);
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
/**
obj 当前对象
sel 方法名称
cls 实例方法为类 类方法为元类
NO cache 没有缓存才会进入这里,有的话,从缓存哪里就返回出去了
*/
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
我们可以看出,这个方法中,会调用lookUpImpOrForward,并传入几个参数cls(当前类,如果是类方法这里就是元类)sel 方法编号,obj (当前对象)以及initialize(YES) 和cache(NO),resilver(NO),继续查看lookUpImpOrForward方法:
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
// 查找方法进入的时候这里是NO
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// 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();
// 内部对class 的判断,是否被编译
checkIsKnownClass(cls);
// 这里是拿出类中的bits中的 class_rw_t 中的 data中的Method查看是否有
//为查找方法做准备条件,判断类有没有加载好,如果没有加载好,那就先加载一下类信息,准备好父类、元类
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
//确定类已经加载完成,
if (initialize && !cls->isInitialized()) {
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
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
//判断该方法从缓存里面查找一遍,如果是方法调用,会先走cache,一般这里imp 是取不到的,别的方法可能会调用这里
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
// 这里的括号是为了行程局部作用域,避免局部变量命名重
{
// 在MethodList 中进行查找方法
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 找到方法后,填充到缓存中去,内部调用cache_fill_nolock
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
//直接跳到done这个点,C语言调用方式goto
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 = 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 {
//如果是_objc_msgForward_impcache方法,就退出,不需要遍历了,因为_objc_msgForward_impcache这个方法就是未实现时候转发用的
// 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;
}
}
}
// No implementation found. Try method resolver once.
//如果方法仍然没找到,就开始做方法消息动态解析了
if (resolver && !triedResolver) {
runtimeLock.unlock();
// 实例方法解析:resolveInstanceMethod
// 类方法解析:resolveClassMethod
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.
//设置为NO,防止因为消息动态解析导致死循环
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
//如果第一次转发还是没用的话,就取出_objc_msgForward_impcache这个方法指针
imp = (IMP)_objc_msgForward_impcache;
//缓存起来将指针返回回去执行
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
这个方法就有几个重点要观察的:
-
runtimeLock.lock()
为了防止多线程操作 -
realizeClassMaybeSwiftAndLeaveLocked 为查找方法做准备条件,如果类没有初始化时,初始化类和父类、元类、分类等
-
imp = cache_getImp(cls, sel)
为了容错从缓存
中再找一遍,如果找到直接goto done(8)
-
局部作用域中查找本类getMethodNoSuper_nolock中,如果找到进行log_and_fill_cache 保存缓存直接
goto done(8)
-
局部作用与中在父类中进行查找首先
在父类缓存
中查找,若有直接log_and_fill_cache
并goto done
;没有再去父类的方法列表中
查找方法,若有直接log_and_fill_cache
并goto done
(8)
-
如果还没找到就动态方法解析
_class_resolveMethod
,标记为triedResolver = YES(已自我拯救过)
,动态方法解析结束后跳转慢速流程第三步进行再一次查找
-
如果动态方法解析之后再找一遍仍然没找到
imp
,就抛出错误_objc_msgForward_impcache
得到imp
并cache_fill
-
done
:多线程解锁,返回imp
这个方法cache_getImp 后续进行详细解释
getMethodNoSuper_nolock 方法
这里对其中部分代码进行解释第四步getMethodNoSuper_nolock 方法遍历调用search_method_list方法
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过程之后,通过findMethodInSortedMethodList(sel, mlist)进行二分查找,保证能够快速寻找到目标方法
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 = 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;
}
// 如果keyValue > probeValue 则折半向后查询
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
log_and_fill_cache -->cache_fill--->cache_fill_nolock连续调用这个方法可以看类的分析下
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
// 写入缓存
cache_fill (cls, sel, imp, receiver);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
_class_resolveMethod
动态方法解析:在找不到imp时的自我拯救操作 ,下面会进行分析
_objc_msgForward_impcache方法
这个方法中就是如果方法在本类,父类都找不到方法后执行到这里_objc_msgForward_impcache 方法
extern void _objc_msgForward_impcache(void);//这个方法会进入汇编
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] //调用_objc_forward_handler 方法
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
_objc_msgForward_impcache这个方法调用到汇编之后,汇编方法又会调用会C方法_objc_forward_handler
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;
#else
// 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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
这里我们就能看到熟悉的unrecognized selector sent to instance 崩溃信息了
消息转发机制初探
我们已经看到了如果没有实现方法,然后调用,系统会直接崩溃,那么如何避免这些问题呢,苹果大大给我们提供了另一个解决思路,就是消息转发机制,这篇我们先进行一个初步的查看
首先我们现在main函数中调用一个没有实现的方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
#pragma clang diagnostic push
// // 让编译器忽略错误,不然调用没有的方法,编译器编译完语法分析会报错,导致不能运行
#pragma clang diagnostic ignored "-Wundeclared-selector"
XZTeacher *teacher = [[XZTeacher alloc] init];
// 消息转发流程这个方法.h中之声明,不实现
[teacher saySomthing];
#pragma clang diagnostic pop
}
return 0;
}
根据上面文章的分析,我们很快可以定位到,如果都没有找到这个方法,那么就会走到动态解析方法_class_resolveMethod
static void resolveMethod(Class cls, SEL sel, id inst)
{
runtimeLock.assertUnlocked();
assert(cls->isRealized());
if (! cls->isMetaClass()) {//不是元类
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
resolveInstanceMethod(cls, sel, inst);
}
}
}
因为外部我们写的是实力方法,我们这里先看以下方法resolveInstanceMethod方法
static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
runtimeLock.assertUnlocked();
assert(cls->isRealized());
//查找方法中是否有实现resolveInstanceMethod这个方法
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
//发送SEL_resolveInstanceMethod消息,
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//发送消息SEL_resolveInstanceMethod 传参为我们的方法编号
//崩溃的方法不是这个方法,说明再NSObject方法中有实现这个方法
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
//再次查找类中是否sel方法,因为resolveInstanceMethod方法执行后可能动态进行添加了,resolver是不要进行消息转发了
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));
}
}
}
首先判断类有没有实现SEL_resolveInstanceMethod方法,其实也就是+ (BOOL)resolveInstanceMethod:(SEL)sel方法,我们通过在源码搜索可以看到在NSObject类中已经都实现了,并且都是一个空方法,并且返回NO,
//在NSObject.mm 文件中有看到这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
在lookUpImpOrNil 方法中实际就是有调用了一次方法查找流程这里的参数是NO( initialize) YES (Cache ) NO (resolver)不会造成递归
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;
}
这里我们看到系统会自动调用+resolveInstanceMethod方法,这说明我们就有一些可操作性,那么我们在类中实现以下这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"XZTeacher 走了resolveInstanceMethod %p",sel);
return [super resolveInstanceMethod:sel];
}
运行程序看到输出结果,我们可以看到执行了我们输出日志,但是崩溃了,这里我们就考虑我们是可以做一些操作
这里我们可以考虑到这里实际上的一些列操作就是为了找到函数的IMP ,那我们是不是可以直接在这里将函数IMP实现使得下面lookUpImpOrNil方法的时候可以找到IMP,程序就不会崩溃了;
具体实现方法,引入头文件#import <objc/message.h> 如下所示:
#import <objc/message.h>
- (void)sayHello{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(saySomthing)) {
NSLog(@"进入方法了");
IMP saySomeIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayType = method_getTypeEncoding(sayMethod);
return class_addMethod(self, sel, saySomeIMP, sayType);
}
return [super resolveInstanceMethod:sel];
}
我们来看一下输出结果:
可以看出我们在调用saySomthing 方法的时候进入了我们写的代码,并且还调用了我们想让他调用的sayHello方法,而且没有崩溃;
总结
这篇文章主要是根据上篇文章职工objc_msgSend方法快速查找流程汇编过程找到_class_lookupMethodAndLoadCache3 方法,并进行分析了这个代码中消息查找的流程,并对消息转发防止崩溃进行了初步的了解,如果有错误的地方还请指正,大家一起讨论,(文章中有错误后,我发现会第一时间对博文进行修改),还望大家体谅!
欢迎大家点赞,关注我的CSDN,我会定期做一些技术分享!
写给自己
时光流逝,讨厌的人会带着讨人厌的话离开,喜欢的人会带着美好的事到来。把目光放在远处,洒脱还给自己。忘掉路人,放过自己,未完待续...