1、objc_msgSend 简介
在 OC 中,所有的方法调用底层都会转换成 objc_msgSend 函数进行调用,例如,对于下面方法调用:
[myObject test1];
[myObject test2:100];
将其转换成 C++ 源码:
((void (*)(id, SEL))(void *)objc_msgSend)((id)myObject, sel_registerName("test1"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)myObject, sel_registerName("test2:"),100);
简化处理后:
objc_msgSend(myObject, sel_registerName("test1"));
objc_msgSend(myObject, sel_registerName("test2:"),100);
可在源码中找到 objc_msgSend 函数定义:
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
objc_msgSend 函数会有一个可能为空的返回值,一般来说需要至少两个参数:消息调用者及方法名,以及不确定个数的方法参数。
objc_msgSend 伪代码如下:
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
其实 objc_msgSend 主要做的就是获取 IMP 并调用,并且在找到对应方法实现 IMP 调用的时候,会默认带入两个参数:self 和 _cmd,_cmd 就是 SEL。所以我们可以在方法里使用 self 和 _cmd。
实际上,objc_msgSend 是使用汇编来实现的,采用汇编来实现,主要是因为有的方法返回 id,有的返回 int,单独一个方法定义满足不了多种类型返回值,汇编对可变参数处理起来更方便。再加上汇编程序执行效率高,所以 objc_msgSend 这种高频率调用的函数使用汇编实现有助于提高系统运行效率。
2、objc_msgSend 源码分析
(1) 方法快速查找流程(汇编实现)
源码下载地址:opensource.apple.com/tarballs/ob…
objc_msgSend 相关源码主要在 objc-msg-arm64.s 中,从源码可以看出 objc_msgSend 是使用汇编实现的。
objc_msgSend 部分源码如下:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// 检查 objc_msgSend 第一个参数(消息接受者)是否为 nil
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
// 如果支持 tagged pointer,如果 p0 <= 0,执行 LNilOrTagged 函数
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
......
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
......
b LGetIsaDone
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
首先会对 objc_msgSend 的第一个参数是否为 nil,即消息接收者是否存在:
- 如果不存在,函数将直接 return,所以给
nil对象发送消息不会引发 crash。 - 如果存在,则继续执行,进入缓存查找逻辑
CacheLookup(传入的参数是 NORMAL)。
CacheLookup 源码如下:
.macro CacheLookup
// p1 = SEL, p16 = isa(p1 存放着 SEL,p16 存放这当前消息接收者的 isa 指针)
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
.macro 在汇编中用于定义宏,所以 CacheLookup 是被定义成了宏。
CacheLookup 的功能其实就是去当前类的方法缓存中去查找方法:
- 如果在缓存中找到方法,即命中,会调用
CacheHit,直接调用对应方法或返回方法 imp。 - 如果没有找到方法,则会调用
CheckMiss。
CheckMiss 源码如下:
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
因为前面 CacheLookup 传入的参数是 NORMAL,所以这里 CheckMiss 调用了 __objc_msgSend_uncached:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
在 __objc_msgSend_uncached 中,调用了 MethodTableLookup:
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
可以看到,MethodTableLookup 中又调用了 _lookUpImpOrForward。
并且调用 lookUpImpOrForward 函数时传入参数如下:
x0 = receiver
x1 = selector
x2 = class
x3 = LOOKUP_INITIALIZE | LOOKUP_RESOLVER
到这里,快速查找流程结束,进入慢速查找流程。
(2) 方法慢速查找流程(C/C++ 实现)
_lookUpImpOrForward 在 C++ 中对应的函数为 lookUpImpOrForward,位于 objc-runtime-new.m 中:
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();
if (fastpath(behavior & LOOKUP_CACHE)) {
// cache_getImp 为汇编实现,内部实际上就是执行前面快速查找流程
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// 加锁
runtimeLock.lock();
// 查询是否为已知类
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())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;) {
// 当前类方法列表(如果方法列表已排序采用二分查找算法,否则直接遍历查找),如果找到,则返回,将方法缓存到 cache 中。
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
// 跳转到 done
goto done;
}
// curClass = superclass 继续查找,一直到 curClass == nil 的时候让 imp = forward_imp, break,终止循环
if (slowpath((curClass = curClass->superclass) == nil)) {
// 没找到,也没有父类了,使用转发
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.");
}
// 获取父类的缓存
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// 父类中找到的是forward_imp,则终止查找
break;
}
if (fastpath(imp)) {
// 找到imp,则跳转到 done,进行方法缓存
goto done;
}
}
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;
}
通过源码可以发现,是通过 getMethodNoSuper_nolock 函数去类的方法列表查找方法:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
通过函数中以下源码:
auto const methods = cls->data()->methods();
cls->data() 返回的就是 class_rw_t:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
可知,查找的方法列表就是 class_rw_t 中的 methods :
getMethodNoSuper_nolock 函数中读取了方法列表后,具体 sel 查找是通过 search_method_list_inline 函数:
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
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 函数进行查找,findMethodInSortedMethodList 函数实际上进行的是进行二分查找,通过二分查找提高查找效率。否则直接遍历去查询方法列表。
lookUpImpOrForward 源码分析:
-
1、对类做合法性检查及处理,判断是否时已知类、类是否实现、是否已经初始化,如果类没有实现或初始化,进行一下对应的实现及初始化操作。
- 函数的
behavior参数value为LOOKUP_INITIALIZE | LOOKUP_RESOLVER,所以不再去查找缓存(因为之前已经查找过缓存了)。
- 函数的
-
2、到当前类的方法列表中、查询此方法,如果找到方法,则将方法(
Method中的IMP)存放到当前类的cache中去,并且返回 IMP,方法结束。如果没找到,则执行第 3 步。- 此处当前类的方法列表即
class_rw_t中的methods。 - 查找方法列表前会对否排好序进行判断,如果方法列表已排序,使用二分查找算法进行查找,提高查找效率。否则直接遍历去查询方法列表。
- 此处当前类的方法列表即
-
3、递归向父类中查找,先查找缓存,再查找类方法列表,如果找到方法,执行第 4 步(
forward_imp判断)。如果没有找到,imp = forward_imp,然后执行第 4 步。 -
4、判断此方法是否是
forward_imp方法,如果不是,则将此方法存放到当前类的cache中,并且返IMP,方法结束。如果此方法是forward_imp,直接执行第 5 步。forward_imp实际上就是:_objc_msgForward_impcache。_objc_msgForward_impcache其实是一个存放在内存中的函数指针,为汇编实现,内部会调用__objc_msgForward函数(消息转发的函数)。
-
6、判断当前是否执行执行过方法解析(动态方法决议),如果没有执行过,进入动态方法解析阶段。如果执行过 1 次动态方法解析,则走到消息转发流程。
(3) 动态方法解析(动态方法决议)
根据前面 lookUpImpOrForward 源码可知,在方法查找时,如果发现方法是 forward_imp,会判断是否执行执行过方法解析,如果没有执行过,进入动态方法解析阶段,进入 resolveMethod_locked 函数。
resolveMethod_locked 函数源码如下:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// 不是元类
// try [cls resolveInstanceMethod:sel]
// 对象方法的动态方法解析
resolveInstanceMethod(inst, sel, cls);
}
else {
// 是元类
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 类方法的动态方法解析
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
// 对象方法的动态方法解析
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
其中调用的 lookUpImpOrNil 函数源码如下:
static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}
可以看到,lookUpImpOrNil 函数内部就是直接调用了 lookUpImpOrForward 函数,重新进入慢速查找流程,注意这里 behavior 的参数值,进到 lookUpImpOrForward 里后是需要先去缓存中查找方法的(快速查找流程)。
resolveInstanceMethod 函数源码如下:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
// 如果你没有实现类方法 +(BOOL)resolveInstanceMethod:(SEL)sel
// NSObject 已经实现了,所以一般不会走这里
return;
}
// 调用类方法: +(BOOL)resolveInstanceMethod:(SEL)sel,
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// 检测是否有 sel 对应的 IMP。如在 +(BOOL)resolveInstanceMethod:(SEL)sel 中动态添加了 sel 对应方法,此时再次去查找这个 IMP 就能找到,并且在这一步就会将其保存到缓存中,下次调用从缓存中就可以找到了。
// 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(inst, sel, cls);
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));
}
}
}
resolveInstanceMethod 函数源码解析:
-
如果实现了
+(BOOL)resolveInstanceMethod:(SEL)sel方法直接调用该方法。 -
调用
+(BOOL)resolveInstanceMethod:(SEL)sel方法后,还会调用一次lookUpImpOrNil函数查找一下方法。所以如果在+(BOOL)resolveInstanceMethod:(SEL)sel方法中动态添加了sel对应方法之后,调用lookUpImpOrNil函数就能找到对应imp,并且会将方法保存到缓存里,下次就能直接从缓存中找到并调用了。 -
根据源码可发现
+(BOOL)resolveInstanceMethod:(SEL)sel方法返回值并没什么实际用处,只用来做日志打印而已,所以返回 YES 还是 NO 都无所谓。但是在实际开发的时候还应当按照 OC 规范去做,处理了 sel 就返回 YES,否则返回 NO。
resolveClassMethod 主要逻辑和 resolveInstanceMethod 基本一样,源码如下:
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(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(inst, sel, cls);
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",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
动态方法解析总结:
-
如果不是元类(说明当前是对象方法),进行对象方法的动态方法解析。
- 如果实现了
+(BOOL)resolveInstanceMethod:(SEL)sel方法并且在方法中动态添加了sel对应方法,后面调用lookUpImpOrNil函数就能找到对应imp,并且会将方法保存到缓存里。下次就能直接从缓存中找到sel并调用了。
- 如果实现了
-
如果是元类(说明当前是类方法),先进行类方法的动态方法解析,如果没在
+ (BOOL)resolveClassMethod:(SEL)sel中动态添加sel对应方法,则执行一次对象方法解析。- 上面如果
!lookUpImpOrNil(inst, sel, cls)条件成立,说明执行过类方法的动态方法解析之后仍然没有找到sel,也就是没在+ (BOOL)resolveClassMethod:(SEL)sel中动态添加sel对应方法。 - 这里为什么要再执行一次对象方法解析?因为元类的父类是根元类,根元类的父类是根类(NSObject)。如果整个继承链都没找到
+(BOOL)resolveClassMethod:(SEL)sel方法,最终会找到根类(NSObject),根类是 class 对象,class 对象中保存的是对象方法,所以需要再做一次对象方法动态方法决议。
- 上面如果
对象方法、类方法动态方法解析查找路径:
- 对象方法:类 -> 父类 -> 根类 -> nil
- 类方法:元类 -> 根元类 -> 根类 -> nil
根据以上查找路径可知,无论是对象方法的动态方法解析还是类方法的动态方法解析,都会查找到根类(NSObject),所以都会走到根类(NSObject)的 +(BOOL)resolveInstanceMethod:(SEL)sel 方法。也就是说,我们可以直接使用 +(BOOL)resolveInstanceMethod:(SEL)sel 方法处理对象方法和类方法的动态方法解析。
动态方法解析 Demo:
// 对象方法的动态方法解析
- (void)myInstanceMethod {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// 调用了未实现的对象方法 xxxxInstanceMethod
if (sel == @selector(xxxxInstanceMethod)) {
Method resolveMethod = class_getInstanceMethod(self, @selector(myInstanceMethod));
IMP myInstanceIMP = method_getImplementation(resolveMethod);
const char* types = method_getTypeEncoding(resolveMethod);
NSLog(@"%s", types);
return class_addMethod(self, sel, myInstanceIMP, types);
}
return [super resolveInstanceMethod:sel];
}
// 类方法的动态方法解析
+ (void)myClassMethod {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel {
// 调用了未实现的类方法 xxxxClassMethod
if (sel == @selector(xxxxClassMethod)) {
Method resolveMethod = class_getClassMethod(self, @selector(myClassMethod));
IMP myClassIMP = method_getImplementation(resolveMethod);
const char* types = method_getTypeEncoding(resolveMethod);
NSLog(@"%s", types);
return class_addMethod(object_getClass(self), sel, myClassIMP, types);
}
return [super resolveClassMethod:sel];
}
也可以全都在 + (BOOL)resolveInstanceMethod:(SEL)sel 方法中处理:
- (void)myInstanceMethod {
NSLog(@"%s",__func__);
}
+ (void)myClassMethod {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(xxxxInstanceMethod)) {
// 调用了未实现的对象方法 xxxxInstanceMethod
Method resolveMethod = class_getInstanceMethod(self, @selector(myInstanceMethod));
IMP myInstanceIMP = method_getImplementation(resolveMethod);
const char* types = method_getTypeEncoding(resolveMethod);
NSLog(@"%s", types);
return class_addMethod(self, sel, myInstanceIMP, types);
} else if (sel == @selector(xxxxClassMethod)) {
// 调用了未实现的类方法 xxxxClassMethod
Method resolveMethod = class_getClassMethod(self, @selector(myClassMethod));
IMP myClassIMP = method_getImplementation(resolveMethod);
const char* types = method_getTypeEncoding(resolveMethod);
NSLog(@"%s", types);
return class_addMethod(object_getClass(self), sel, myClassIMP, types);
}
return NO;
}
(4) 消息转发
根据 lookUpImpOrForward 函数实现可知,如果执行过方法动态解析仍然没有找到对应方法,则走到消息转发流程,lookUpImpOrForward 方法也会把需要消息转发的 _objc_msgForward_impcache 作为 imp 返回。
_objc_msgForward_impcache 在汇编中实现如下:
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_msgForward_impcache 是一个内部函数指针,最终会拿到 __objc_forward_handler 的地址并调用,其 OC 实现如下:
__attribute__((noreturn, cold)) 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;
其中 _objc_fatal 作用就是打日志并调用 __builtin_trap() 触发 crash,根据以上源码可知,这里是将 objc_defaultForwardHandler 赋值给 _objc_forward_handler,但是再看下 objc_defaultForwardHandler 实现发现,这里只是 crash 并打印信息,也是那个熟悉的 crash:unrecognized selector sent to instance xxxxxxxx,并没有其他内容。
所以,要想实现消息转发,就需要在某处给 _objc_forward_handler 重新赋值:
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;
#if SUPPORT_STRET
_objc_forward_stret_handler = fwd_stret;
#endif
}
上面 _objc_forward_stret_handler 是用在非 arm64 下的,这里不再深究。
对 objc_setForwardHandler 的调用,以及之后的消息转发相关方法调用栈,是在 CoreFoundation 中的,但是 Apple 故意在相关开源的代码中删除了在 CFRuntime.c 文件 __CFInitialize() 中调用 objc_setForwardHandler 的代码(CoreFoundation 源码地址)。
借助逆向可分析下:
根据逆向结果可以发现,
__CFInitialize() 中是以 __CF_forwarding_prep_0 和 ___forwarding_prep_1___ 作为参数调用的。
接下来制造一个简单 crash 查看方法调用栈:
MessageCrashTest[7942:944693] -[TestObject test]: unrecognized selector sent to instance 0x100508d80
MessageCrashTest[7942:944693] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestObject test]: unrecognized selector sent to instance 0x100508d80'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff34801b57 __exceptionPreprocess + 178
1 libobjc.A.dylib 0x00007fff6d6765bf objc_exception_throw + 48
2 CoreFoundation 0x00007fff34880be7 -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x00007fff347663bb ___forwarding___ + 1009
4 CoreFoundation 0x00007fff34765d98 _CF_forwarding_prep_0 + 120
5 MessageCrashTest 0x0000000100000f26 main + 79
6 libdyld.dylib 0x00007fff6e81ecc9 start + 1
)
可以看到 _CF_forwarding_prep_0 函数内部调用了 ___forwarding___ 函数,然后调用 doesNotRecognizeSelector 方法,最后抛出异常。
___forwarding___ 也是不开源的,但是国外大神通过逆向分析后写了伪代码( 链接 ):
void __forwarding__(BOOL isStret, void *frameStackPointer, ...) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 4);
Class receiverClass = object_getClass(receiver);
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
const char *className = class_getName(object_getClass(receiver));
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix);
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"-[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
sel_getName(sel),
receiver);
<breakpoint-interrupt>
}
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
sel_getName(sel),
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature
frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
const char *selName = sel_getName(sel);
SEL *registeredSel = sel_getUid(selName);
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} else if (class_respondsToSelector(receiverClass, @selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
其逻辑大致如下:
- 1、调用
forwardingTargetForSelector,会返回一个新的target,使用这个target作为新的receiver执行selector,如果返回内容为nil或者和旧receiver一样,进行第 2 步。 - 2、调用
methodSignatureForSelector获取方法签名后,判断返回类型信息是否正确,然后调用forwardInvocation执行NSInvocation对象,并将结果返回。如果对象没实现methodSignatureForSelector方法,进行第 3 步。 - 3、调用
doesNotRecognizeSelector方法。
也就是说,调用了未实现的方法,如果在动态方法解析阶段未处理,我们还可以在消息转发阶段通过以下处理方式避免 crash,例如:
假设调用了当前类(AObject)中未实现的如下方法,且没有在动态方法解析阶段动态处理该方法:
- (int)test:(int)arg;
我们现在将其转发到 BObject 中的如下相同方法:
- (int)test:(int)arg;
**方式 1:**在 AObject 类中实现如下方法转发到 BObject:
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)) {
// 返回一个可以处理方法的对象,需保证对象里有相同方法
return [[BObject alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
如果 AObject 类没有实现该方法,还可以使用下面方式转发消息。
**方式 2:**在 AObject 类中实现如下方法执行 NSInvocation:
// 首先,调用如下方法返回对应方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)) {
// 根据 Types 获取方法签名
// return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
// return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
// 如果 BObject 中实现了相同方法,可以使用下面方法获取方法签名
return [[[BObject alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
// 然后在下面方法中做转发处理
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(test:)) {
// 方法 1:调用 BObject 中同名方法
// [anInvocation invokeWithTarget:[[BObject alloc] init]];
// 方法 2:修改 anInvocation,改为调用 BObject 中其他方法
// anInvocation.target == [[BObject alloc] init]
// anInvocation.selector == xxxxTest:
// [anInvocation invoke];
// 方法 3:直接使用 BObject 的对象调用 test 方法
// [[[BObject alloc] init] test:xxxxx]
// 方法 4:什么都不做也可以,也不会 crash 了
}
}
对于类方法的处理,也是一样的,只不过改调用对应类方法而已:
// 转发方式 1
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)) {
// 注意,由于是类方法,所以这里应当返回 class,当然,如果想转发后调用 BObject 中的对象方法,这里就返回对象(例如:return [[BObject alloc] init];)即可
return [BObject class];
}
return [super forwardingTargetForSelector:aSelector];
}
同理,如果 AObject 类没有实现上面方法,还可以使用下面方式转发消息:
// 转发方式 2
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
// 转发逻辑,或不做任何处理
}
NSInvocation 其他两个常用方法:
- 获取参数
int arg;
// 如果只有一个参数 arg,那么这个参数的 index 是 2,即第三个参数,因为前两个参数是 self 和 _cmd
[anInvocation getArgument:&arg atIndex:2];
- 获取返回值
int ret;
[anInvocation getReturnValue:&ret];
3、消息发送完整流程图
附上一张本人绘制的消息发送完整流程图:
🔗 原文链接:
Runtime 之消息机制 - 李峰峰博客
📢 作者声明:
转载请注明来源