主要探究 objc_msgSend()的流程;
文中使用的 objc4源码是objc-781版本;
runtime 部分一
runtime 部分二
runtime 部分三
1. objc_msgSend()的流程;
首先我们都知道, 我们调用方法后到底层就是通过objc_msgSend()
来实现的, 这个机制就是我们所说的消息发送机制, 这个过程分为消息发送
动态解析
消息转发
三个阶段;
转化为objc_msgSend()
有两个参数, 第一个是接收者receiver
, 第二个是SEL
;
///不论是类方法还是实例方法底层都是objc_msgSend()方式实现;
Person *person = [[Person alloc] init];
[person realizedMehod];
///转化为C++后代码如下
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("realizedMehod"));
由于 objc_msgSend()
的实现过程是通过汇编实现, 本人能力有限, 只能大致推测下, 从 objc_msgSend()
开始的整个流程, 如果有;理解错误的地方还请不吝赐教;
注意汇编中调用 C
或者 C++
的函数会在函数名之前加上_
, 例如objc_msgSend()
在汇编中对应的就是_objc_msgSend
;
///入口
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd, ...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in x17
* x16 reserved for our use but not used
*
********************************************************************/
....
//_objc_msgSend开始
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
///在 arm64架构下SUPPORT_TAGGED_POINTERS = 1 , 所以下一步LNilOrTagged, 检查 receiver 是否为空;
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
///通过 isa 获取 class
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
///开始缓存查找, 注意入参是NORMAL
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#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
===>
///缓存查找
.macro CacheLookup
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
/*
arm64架构执行这里的逻辑;
虽然我们不知道汇编的具体作用, 但是从注释依然可以看出一些信息. 从散列表(buckets)中
通过_cmd & mask来获取方法 IMP, 这个跟之前的方法缓存的算法和过程相对应;
*/
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
...
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// 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
///找到缓存 call or return imp
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
///缓存没有找到, 调用CheckMiss
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
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
===>
///宏定义CheckMiss
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
///之前的入参是NORMAL, 下一步调用__objc_msgSend_uncached
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
===>
///__objc_msgSend_uncached的流程
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
///调用MethodTableLookup
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
===>
.macro MethodTableLookup
...
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
///调用lookUpImpOrForward方法
bl _lookUpImpOrForward
...
下面就是到源码lookUpImpOrForward的实现;
===>
//**********************************************************************************************//
在arm64-asm.h文件中可以得知在 arm64 & __LP64__模式下SUPPORT_TAGGED_POINTERS = 1
#if __arm64__
#if __LP64__
// true arm64
#define SUPPORT_TAGGED_POINTERS 1
....
===
在objc-config.h文件中我们可以得知在 arm64 & __LP64__模式下 CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_1
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
#endif
//**********************************************************************************************//
上面的过程主要是记录下在汇编层面objc_msgSend()
的流程, 下面着重说下lookUpImpOrForward
的实现;
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
标准的 IMP 查找方法
...
* If you don't want forwarding at all, use LOOKUP_NIL.
注意这句话, 如果你完全不想使用消息转发, 则使用LOOKUP_NIL
**********************************************************************/
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
///获取消息转发的 IMP
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
///初始的imp为空
IMP imp = nil;
///当前类
Class curClass;
///runtime锁
runtimeLock.assertUnlocked();
// Optimistic cache lookup
///查找方法缓存, 如果查找到缓存直接结束流程将方法返回
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
///检查类的initialize相关
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;
///核心方法, 通过向上遍历当前类的父类, 来查找imp
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
///获取当前类的方法列表, 不查询父类
Method meth = getMethodNoSuper_nolock(curClass, sel);
///如果当前类中查找到imp则结束查找 去done:出缓存imp然后返回这个imp;
if (meth) {
imp = meth->imp;
goto done;
}
/*
注意这个地方的写法, 执行完这句代码后, curClass就已经指向父类了;
if (slowpath((curClass = curClass->superclass) == nil)) {}
等同于下面两句语句组合, 如果有疑问可以自行测试下;
curClass = curClass->superclass
if (slowpath(curClass == nil) {}
如果查询到基类仍然没有查找大相关方法, 则使用消息转发(注意这里只是跳出循环)
实际上下面要先判断动态解析, 动态解析是先于消息转发的;
*/
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
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.");
}
// Superclass cache.
/*
从curClass已经指向了父类, 所以这里判断父类中是否有消息转发,
如果子类没有消息转发相关处理, 写在父类中实现消息转发也会有效;
仍然是跳出循环, 先去动态解析;
*/
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
//如果父类中方法缓存中查找到了, 则将方法缓存到本类然后返回imp
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
/*
如果没有找到IMP则开始动态解析, 不论是否成功最后都会再次调用lookUpImpOrForward;
注意只进行一次动态解析, 如果动态解析成功, 则将相关方法缓存到本类;
下次再次调用时则是直接查找类中方法列表即可;
如果动态解析失败, 则再次lookUpImpOrForward, 重新开始流程, 进入消息转发阶段;
具体请看resolveMethod_locked的官方注释和实现流程;
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
///将查找到的imp缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
///如果完全不想使用消息转发, 但是获取到的缓存imp=forward_imp则直接返回nil
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
///最终返回imp
return imp;
}
//******************************************************************//
///动态解析方法入口
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);
}
===>
///以实例方法为例, 动态解析方法入口中会调用 resolveInstanceMethod方法
/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
///寻找要添加到类中的method;
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
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.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, 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(inst, sel, cls);
...
}
至此objc_msgSend()
的流程大致结束, 下面代码测试下动态解析和消息转发;
2. 动态解析
两个关键方法: 实例方法+(BOOL)resolveInstanceMethod:
; 类方法+(BOOL)resolveClassMethod:
以实例方法为例, 简单的理解为:
调用notRealizeMethod
方法后后, 如果本类或者父类实现了这个方法则调用, 否则检查是否实现动态解析方法,
实现了resolveInstanceMethod
方法,开始动态解析; 这个地方就是动态为本类添加一个方法去映射实现notRealizeMethod
;
没有实现resolveInstanceMethod
, 程序crash
抛出unrecognized selector sent to instance;
开始动态解析;
调用未实现的方法notRealizeMethod
Cat *cat = [[Cat alloc] init];
[cat notRealizeMethod];
///Cat的实现如下;
//.h文件
#import
NS_ASSUME_NONNULL_BEGIN
@interface Cat : NSObject
///此方法未实现
- (void)notRealizeMethod;
@end
NS_ASSUME_NONNULL_END
///.m文件为
#import "Cat.h"
#import
@implementation Cat
- (void)HandleNotRealizedMethod {
NSLog(@"HandleNotRealizedMethod : %s", __func__);
}
/*
对notRealizeMethod方法, 没有实现, 调用后;
实现了resolveInstanceMethod方法,开始动态解析;
没有实现resolveInstanceMethod, 程序crash抛出unrecognized selector sent to instance;
开始动态解析
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(notRealizeMethod)) {
SEL handelSel = @selector(HandleNotRealizedMethod);
Method handleMethod = class_getInstanceMethod(self, handelSel);
IMP imp = class_getMethodImplementation(self, handelSel);
/*
为某个类添加Method;
参数1: 为哪个类添加方法;
参数2: 为哪个方法添加实现;
参数3: 方法的具体实现;
参数4: 方法的编码格式;
*/
class_addMethod(self, sel, imp, method_getTypeEncoding(handleMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
///运行后的结果为
2020-07-04 17:36:59.568045+0800 objc_msgSend[11787:194063] HandleNotRealizedMethod : -[Cat HandleNotRealizedMethod]
3. 消息转发
如果动态解析方法没有实现, 或者没有处理动态解析, 则进入消息转发阶段;
以实例方法为例, 大致流程为:
几个主要的方法;
- (id)forwardingTargetForSelector:()
返回一个能处理notRealizeInstanceMethod的对象;
- (NSMethodSignature *)methodSignatureForSelector:()
方法签名;
- (void)forwardInvocation:()
最终的处理
注意: 类方法也有消息转发;
把相关的方法打出后手动改为+
即可;处理的流程跟实例方法类似;
+ (id)forwardingTargetForSelector:()
;
+ (NSMethodSignature *)methodSignatureForSelector:()
;
+ (void)forwardInvocation:()
;
测试代码
///调用方法
Pig *pig = [[Pig alloc] init];
[pig notRealizeInstanceMethod];
[Pig notRealizeClassMethod];
///动态解析阶段, 不处理或者处理不成功, 进入消息转发阶段
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return [super resolveInstanceMethod:sel];
}
/*
aSelector这个时候就是notRealizeInstanceMethod;
这个地方需要返回一个值, 就是返回一个能处理notRealizeInstanceMethod的对象;
例如Piggy也有实例方法notRealizeInstanceMethod, 并且也实现了,这时可以返回Piggy的实例对象;即可处理方法;
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
//如过将下面注释的代码打开, 则直接将消息转发给Piggy的实例对象, 方法签名的相关方法不再生效;
///如果发现Pig对象调用notRealizeInstanceMethod方法, 方法没有实现, 并且动态解析失败, 则将消息转发给Piggy对象;
// if (aSelector == @selector(notRealizeInstanceMethod)) {
// Piggy *piggy = [[Piggy alloc] init];
// /*
// 消息转发的过程, 源码不开源, 并不能找到相关流程, 但是消息转发后有做一个操作就是objc_msgSend( piggy, aSelector);
// 就是让piggy调用aSelector;
// */
// return piggy;
// }
return [super forwardingTargetForSelector:aSelector];
}
/*
如果forwardingTargetForSelector并不能返回一个有效的对象; 开始进入方法签名阶段
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
/*
方法编码, 要返回aSelector的编码格式;
如果返回一个合理的值, 则调用forwardInvocation方法;
*/
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
return sig;
}
/*
到了这一步, 可以做任何操作了
类似KVC是实现了setValue: forUndefinedKey:即使没有相应key, 实现了此方法, 什么都不做也不会崩溃;
NSInvocation封装了一个方法的调用信息; 调用者, 方法, 方法编码;
target: 方法之前的调用者, 可更改为其他调用者;
selector : 需要调用的方法, 可更改为其他方法;
methodSignature : 方法的签名信息; 不可更改;
只要target和selector是配套合理的,methodSignature可以忽略;例如:
anInvocation.target = [Dog class];
anInvocation.selector = @selector(classTest:);
[anInvocation invoke];
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
anInvocation.target = [Dog class];
anInvocation.selector = @selector(classTest:);
[anInvocation invoke];
}
以类方法为例验证另一个问题: 子类中调用没有实现方法, 且没有做方法的消息转发, 但是父类实现了消息转发, 也会有效;
//调用方法
[Pig notRealizeClassMethod];
///Pig的父类Animal的.m实现为
#import "Animal.h"
@implementation Animal
+ (id)forwardingTargetForSelector:(SEL)aSelector {
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
return sig;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"Pig类中没有实现notRealizeClassMethod方法, 且没有做类方法的消息转发, 但是父类实现了消息转发, 也会有效;");
}
@end
2020-07-04 18:16:20.022633+0800 objc_msgSend[15196:261753] Pig类中没有实现notRealizeClassMethod方法,
且没有做类方法的消息转发, 但是父类实现了消息转发, 也会有效;
4. 流程图总结消息发送, 动态解析, 消息转发的过程
-
消息发送阶段的流程:
-
动态解析阶段流程:
-
消息转发流程
类方法的处理流程类似, 把相关的-
号方法换成+
号方法;
文中测试代码
参考文章和下载链接
Apple 一些源码的下载地址
方法的查找顺序
什么是散列表
LP64 结构数据占据多少位
LP64什么意思
汇编和 C 函数的相互调用
iOS 方法签名机制
iOS方法返回值和参数对应的Type Encodings