由于缓存的读取和写入涉及到了 runtime 的知识,在这里做简单的介绍。
id obj = [NSObject alloc]
[xxx performSelector:@selector(xxx)]
多态和运行时就是 runtime 的东西。
Runtime 有两个版本:Legacy 版本(早期版本,对应 Objective-C 1.0) 和 Modern 版本(现行版本 Objective-C 2.0)。
Objective-C 1.0, 32 位的 Mac OS X 的平台。iPhone 程序和 Mac OS X v10.5 及以后的系统中的 64 位程序。modern 版本最显著的变化就是 non-fragile:
legacy 版本,如果你改变类中的实例变量的布局,必须从它继承的类重新编译。modern 版本,如果你改变类中的实例变量的布局, 不必从它继承的类重新编译。另外 modern 版本支持实例变量合成为声明的属性 Declared Properties[1]。
⚠️ runtime就是c/c++/汇编写的一套API
runtime官方文档[2] 对于苹果的一些文档资料都可以在这里搜索:苹果官方文档网址[3],不过苹果现在不怎么维护文档了。
Objective-C 程序有三种途径和运行时系统交互:
Objective-C 代码,比如:[obj method]。Foundation 框架中 NSObject 的方法,比如:isKindofClass。API 接口,比如:class_getInstanceSize。对应的结构图如下:

最终会通过编译器与运行时系统交互。
大多数情况下,运行时系统自动工作。我们仅仅需要编写和编译 Objective-C 源码,编译器会创建实现动态语言特性的数据结构和函数调用。比如:
HPObject *obj = [HPObject alloc];
[obj sayHello];
编译器编译后就会变成:
HPObject *obj = ((HPObject *(*)(id, SEL))(void *)objc_msgSend
((id)objc_getClass("HPObject"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("sayHello"));
在 Cocoa 中的大多数对象是 NSObject 的子类( NSProxy 除外),大多数类继承了它所定义的方法。在少数情况下 NSObject 类仅仅定了如何去做的模板,并不提供所有必须的实现。
NSProxy底层也是runtime那一套,也是继承自objc_object:
#ifndef _REWRITER_typedef_NSProxy
#define _REWRITER_typedef_NSProxy
typedef struct objc_object NSProxy;
typedef struct {} _objc_exc_NSProxy;
#endif
struct NSProxy_IMPL {
Class isa;
};
NSProxy *proxy = ((id _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSProxy"), sel_registerName("alloc"));
比如 description 方法,主要用于调试。NSObject 对它的实现并不知道调用的类包含什么,因此他返回一个描述对象的名称和地址的字符串。NSObject 的子类可以实现方法返回更多信息。例如,Foundation的 NSArray 返回它所包含对象列表的信息。
一些 NSObject 方法简单地查询 runtime system 的信息。这些方法允许对象进行自我检查。比如:
isKindOfClass: 和 isMemberOfClass: 测试了类在继承链中的位置;respondsToSelector: 对象是否能接受一个特定消息;conformsToProtocol: 对象是否声明实现了一个特定协议中的方法;methodForSelector: 提供了方法实现的地址。像上面这样的方法提供了对象内省的能力。runtime 是一个提供了一套公共接口(函数和数据结构)的动态分享库,头文件在 /usr/include/objc。许多 Objective-C 代码可以通过 runtime 的 api 替换为 C 实现。文档: Objective-C Runtime Reference[4]。
Interacting with the Runtime[5]
有如下代码:
#import <Foundation/Foundation.h>
@interface HPObject : NSObject
- (void)sayHello;
- (void)sayHelloAgain;
@end
@implementation HPObject
- (void)sayHello {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
HPObject *obj = [HPObject alloc];
[obj sayHello];
[obj sayHelloAgain];
}
return 0;
}
clang 编译成 c++ 文件查看 main 的实现:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
HPObject *obj = ((HPObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HPObject"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("sayHello"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("sayHelloAgain"));
}
return 0;
}
意味着有一些编译时环境的处理,编译之后上层的代码都会得到一个解释,最终调用了 objc_msgSend。
所以:方法调用 = 发送消息:objc_msgSend(消息接收者,sel)
加个参数再看下编译后的代码:
//编译前:
[obj sayHello:@"message"];
//编译后
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)obj, sel_registerName("sayHello:"), (NSString *)&__NSConstantStringImpl__var_folders_g1_hnpx107d1d3br4s_yg5r763w0000gn_T_main_330cec_mi_1);
方法调用 = 发送消息:objc_msgSend(消息接收者,消息的主体(sel + 参数))
那么就意味着我们自己也可以通过 objc_msgSend 调用方法,为了方便去掉参数调用:
#import <objc/message.h>
[obj sayHello];
objc_msgSend(obj,@selector(sayHello));
objc_msgSend(obj,sel_registerName("sayHello"));
输出:
-[HPObject sayHello]
-[HPObject sayHello]
-[HPObject sayHello]
三种方式调用效果相同。
需要配置让 objc_msgSend 支持多个参数。build setting-> Enable Strict Checking of objc_msg_send Calls 设置为 No,默认值为 Yes(只接收1个)。意味着不让编译器检查。

添加一个 HPObject 的子类 HPSubObject,重写 sayHello如下:
- (void)sayHello {
[super sayHello];
NSLog(@"%s",__func__);
}
编译后代码如下:
static void _I_HPSubObject_sayHello(HPSubObject * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("HPSubObject"))}, sel_registerName("sayHello"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_hnpx107d1d3br4s_yg5r763w0000gn_T_main_a683fc_mi_1,__func__);
}
看到了 objc_msgSendSuper 函数,意味着可以通过这个给父类发消息。
objc_msgSendSuper声明如下:
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
可以看到与 objc_msgSend 不同的是
objc_super 结构如下:
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
#if !defined(__cplusplus) && !__OBJC2__
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
};
需要 receiver 和 super_class,objc2 下不需要 class。
HPObject *obj = [HPObject alloc];
HPSubObject *subObj = [HPSubObject alloc];
struct objc_super superStruct;
superStruct.receiver = subObj;
superStruct.super_class = [HPObject class];//第一查找的类
objc_msgSendSuper(&superStruct, sel_registerName("sayHello"));
superStruct.receiver = obj;
superStruct.super_class = [HPObject class];//第一查找的类
objc_msgSendSuper(&superStruct, sel_registerName("sayHello"));
superStruct.receiver = obj;
superStruct.super_class = [HPSubObject class];//第一查找的类
objc_msgSendSuper(&superStruct, sel_registerName("sayHello"));
输出:
-[HPObject sayHello]
-[HPObject sayHello]
-[HPObject sayHello]//重新方法中调用的super
-[HPSubObject sayHello]
super_class 为第一要查找的类,也就是开始从哪一层开始查找,找不到找父类继续查找。receiver消息接收者。再修改下代码:
// HPObject *obj = [HPObject alloc];
// HPSubObject *subObj = [HPSubObject alloc];
// NSObject *nsObj = [NSObject alloc];
struct objc_super superStruct;
// superStruct.receiver = subObj;
superStruct.super_class = [HPObject class];
objc_msgSendSuper(&superStruct, sel_registerName("sayHello"));
id ret1 = objc_msgSendSuper(&superStruct, sel_registerName("sayHelloAgain"));
// superStruct.receiver = obj;
superStruct.super_class = [HPObject class];
objc_msgSendSuper(&superStruct, sel_registerName("sayHello"));
id ret2 = objc_msgSendSuper(&superStruct, sel_registerName("sayHelloAgain"));
// superStruct.receiver = nsObj;
superStruct.super_class = [HPSubObject class];//第一查找的类
objc_msgSendSuper(&superStruct, sel_registerName("sayHello"));
id ret3 = objc_msgSendSuper(&superStruct, sel_registerName("sayHelloAgain"));
输出:
-[HPObject sayHello]
-[HPObject sayHello]
-[HPSubObject sayHello]
-[HPObject sayHelloAgain] -[HPObject sayHelloAgain] -[HPSubObject sayHelloAgain]
这么看貌似 receiver 传不传都可以,那么 receiver 的作用是什么呢?实现 HPObjct 和 HPSubObject的resolveInstanceMethod 方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;
}
调用一个不存在的方法 sayHello1:
HPObject *obj = [HPObject alloc];
HPSubObject *subObj = [HPSubObject alloc];
struct objc_super superStruct;
superStruct.receiver = obj;
superStruct.super_class = [HPObject class];
objc_msgSendSuper(&superStruct, sel_registerName("sayHello1"));
receiver 分为三种情况:nil,obj,subObj 分别测试,结论如下:
receiver 有值subObj:super_class->resolveInstanceMethod --- receiver->resolveInstanceMethod --- _objc_terminate报错((NSobject)doesNotRecognizeSelectorobj:super_class->resolveInstanceMethod --- super_class->resolveInstanceMethod --- _objc_terminate报错((NSobject)doesNotRecognizeSelector)receiver 没有值:super_class->resolveInstanceMethod --- (NSobject)doesNotRecognizeSelector报错结论:父类处理不了的消息会发送给 receiver。receiver 相当于是个备胎。具体的逻辑会在后续的消息查找转发的时候分析。
方法的本质:消息接收者通过 sel 查找 imp 的过程。
上一篇文章中分析 cache 的调用时机是确认了如下调用顺序:__objc_msgSend_uncached(汇编)->lookUpImpOrForward->log_and_fill_cache->cls->cache.insert。最终是在 objc_msgSend_uncached 的汇编发起的调用。
在 objc-cache.mm 文件中有以下说明:
* Cache readers (PC-checked by collecting_in_critical())
* objc_msgSend*
* cache_getImp
*
* Cache readers/writers (hold cacheUpdateLock during access; not PC-checked)
* cache_t::copyCacheNolock (caller must hold the lock)
* cache_t::eraseNolock (caller must hold the lock)
* cache_t::collectNolock (caller must hold the lock)
* cache_t::insert (acquires lock)
* cache_t::destroy (acquires lock)
这说明 cache 的读取是与 cache_getImp 与 objc_msgSend 相关的。(写入逻辑肯定是在读取逻辑之后的,先判断有没有缓存,没有的话才去查找再存)所以要研究写入就要先搞清楚是怎么读取的。

搜索后发现 cache_getImp 的实现是在汇编中的,本片文章以 arm64 架构进行分析。
汇编代码函数名需要比调用多一个_,这个是格式规定。
objc_msgSend 打断点发现是 libobjc.A.dylib 库的:

汇编相关的指令可以参考我之前的文章汇编-循环、选择、判断
源码解读如下:
//入口
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
//p0为调用方的self,判断是否nil,cmp不影响寄存器的值。做减法只影响标记寄存器的值。
cmp p0, #0 // nil check and tagged pointer check
//tagged pointer true amr64
#if SUPPORT_TAGGED_POINTERS
//取寄存器的值。大于直接往下执行不跳转,小于等于(less than or equal to)跳转执行标号。也就是说 <=0 跳转 LNilOrTagged,否则往下执行。
//<=0 跳转 LNilOrTagged标签 是nil或者TAGGED_POINTERS。通过最高位来判断是否负数,为1为负数则是TAGGED_POINTERS,否则是正常指针。
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//arm64_32
// b.eq 等于(equal) 执行标号,否则继续执行。
//==0 消息为空 则跳转 LReturnZero
b.eq LReturnZero
#endif
//x0为self,赋值给p13,也就是isa给到p13
ldr p13, [x0] // p13 = isa
//执行 GetClassFromIsa_p16 参数为(isa,1,self) 通过isa->cls
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
//上面这里通过receiver -> class,到这里就已经通过isa获取了cls了。
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//查找cache CacheLookup(NORMAL,_objc_msgSend,__objc_msgSend_uncached)
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// == 0 跳转LReturnZero
b.eq LReturnZero // nil check
// TAGGED_POINTERS获取cls p16 = cls
GetTaggedClass
//跳转 LGetIsaDone进入缓存查找流程
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
核心逻辑如下:
true arm64 或者 arm64_32 分别执行通过 isa 查找 cls。true arm64 会先判断是否 TAGGED POINTER,是则通过 GetTaggedClass 查找 cls,否则通过 GetClassFromIsa_p16。GetTaggedClass 与 GetClassFromIsa_p16 都是通过 isa 获取 cls。cls 后通过 CacheLookup 缓存中查找方法。LReturnZero 清空寄存器的值然后返回。伪代码实现:
void objc_msgSend(self,_cmd) {
if (__LP64__) {//true arm64
if (isa == 0) {
//清空寄存器&return
return
} else if(isa < 0) {//TAGGED_POINTERS
//TAGGED_POINTERS的cls
cls = GetTaggedClass
} else {//
//isa->cls
cls = GetClassFromIsa_p16(isa,1,isa)
}
} else {//arm64_32
if (isa == 0) {
//清空寄存器&return
return
}
//isa->cls
cls = GetClassFromIsa_p16(isa,1,isa)
}
//找缓存
CacheLookup(NORMAL, _objc_msgSend, __objc_msgSend_uncached)
}
通过 isa 获取 cls 分为两种,一种是 TAGGED POINTER,一种是普通的指针(有可能不是纯 isa,有可能是 nonpointer。这里 TAGGED POINTER 与 nonpointer 是两个概念)。
// Look up the class for a tagged pointer in x0, placing it in x16.
.macro GetTaggedClass
//target pointer 为 标志位(1)extended(8)指针/数据(52)tag(3)
// x10 = isa & 111 获取后3位tag
and x10, x0, #0x7 // x10 = small tag
// x11 = isa >> 55 = 标志 + extended
asr x11, x0, #55 // x11 = large tag with 1s filling the top (because bit 63 is 1 on a tagged pointer)
//判断是否有extended tap == 7 代表有。
cmp x10, #7 // tag == 7?
//tag == 7 ? x12 = x11 : x12 = x10;
//也就是tag == 7 ? x12 = 标志 + extended : x12 = tag;
csel x12, x11, x10, eq // x12 = index in tagged pointer classes array, negative for extended tags.
// The extended tag array is placed immediately before the basic tag array
// so this looks into the right place either way. The sign extension done
// by the asr instruction produces the value extended_tag - 256, which produces
// the correct index in the extended tagged pointer classes array.
// x16 = _objc_debug_taggedpointer_classes[x12]
//adrp 以页为单位寻址 x10 + @PAGE 然后低12位清零。_objc_debug_taggedpointer_classes@PAGE表示内存中第几页
adrp x10, _objc_debug_taggedpointer_classes@PAGE
//x10 = x10 + _objc_debug_taggedpointer_classes@PAGEOFF 表示内存中偏移值
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
// x16(cls) = x10 + x12 << 3 获取cls
ldr x16, [x10, x12, LSL #3]
.endmacro
核心逻辑:
tag。extended。extended。index。cls,通过偏移。
target pointer分布:标志位(1)extended(8)指针/数据(52)tag(3) 具体可以查看 OC类探索1.3.2
伪代码实现:
Class GetTaggedClass(){
tag = isa & 111
// 标志位 + extended
x11 = isa >> 55
x12 = tag == 7 ? x11 : tag
x10 = x10 << 12 + @PAGE
x10 = x10 + @PAGEOFF
//通过偏移找到cls
cls = x16 = x10 + x12 << 3
return cls
}
在 _objc_msgSend 的调用中代码为:GetClassFromIsa_p16 p13, 1, x0,其中 p13 为 isa。
//获取cls给到p16
//p13, 1, x0 isa & mask -> cls
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
//arm64_32
#if SUPPORT_INDEXED_ISA
// Indexed isa
//x16 = isa
mov p16, \src // optimistically set dst = src
//tbz测试位为0则跳转。p16的 第0位为0则跳转 1: 也就是是否支持32位的nonpointer,0表示不支持,不是nonpointer指针。那就是纯地址直接返回就好了。
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
// x10 >> 12 + PAGE
adrp x10, _objc_indexed_classes@PAGE
// x10 + PAGEOFF 这个时候找到的就是classes数组
add x10, x10, _objc_indexed_classes@PAGEOFF
//ubfx 无符号位域提取指令 从p16第2位开始提取,提取15位。也就是提取的 indexcls (2-16)剩余高位补0。 ISA_INDEX_SHIFT = 2 和 ISA_INDEX_BITS = 15
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
// p16 = x10[indexcls] = cls PTRSHIFT 64位3,32位2,这里为2。 ⚠️ UXTP暂时没有懂什么意思。
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
//64位 needs_auth _objc_msgSend 传进来的是1
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
//不需要认证直接将 isa 给到 p16
mov p16, \src
.else
// 64-bit packed isa
//p16 = ExtractISA(p16,isa,self) = cls
ExtractISA p16, \src, \auth_address
.endif
#else
//这里就是上面 tbz p16跳转过来的逻辑
//32位纯指针的情况,直接将isa给到p16
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
核心逻辑如下:
GetClassFromIsa_p16 的目的就是获取 cls 给到 p16。nonpointer,会根据 indexcls 找到 cls。nonpointer,直接 cls 赋值给 p16needs_auth 为 1 时执行 ExtractISA(p16,isa,self)。(_objc_msgSend 传递的时候写死了 1)needs_auth 为 0 时直接赋值 isa 给 p16。也就是 needs_auth 为 0 的时候是纯指针。伪代码实现:
//objc_msgSend传过来的needs_auth为1
void GetClassFromIsa_p16(isa,needs_auth,auth_address) {
static getCls
if (arm64_32) {
if (nonpointer) {
class[] = (isa >> 12 + PAGE) << 12 + PAGEOFF
indexcls = ubfx 提取 isa(2-16)
getCls = class[indexcls]
} else {
getCls = isa;
}
} else {
if (needs_auth) {
//ExtractISA(getCls, isa, self)
getCls = isa & isa_mask
} else {
getCls = isa;
}
}
}
ExtractISA
//p16 isa self
#if __has_feature(ptrauth_calls)
//A12以及以上设备 iphoneX以后
//p16 isa self
.macro ExtractISA
// p16 = isa & ISA_MASK
and $0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
//直接去掉认证信息。硬件级别的。
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
mov x10, $2
// x10 #0x6AE1 << 48 movk: Move 16-bit immediate into register, keeping other bits unchanged.
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
//autda In the general-purpose register or stack pointer that is specified by <Xn|SP> for AUTDA.
autda $0, x10
#endif
.endmacro
#else
//直接isa&mask给到p16
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
核心逻辑是 isa & ISA_MASK 计算出 cls 给到 p16。iphone x 以后设备会有指针认证逻辑,这里有拿到真正地址逻辑,具体指令不是很熟悉。 ISA_SIGNING_AUTH_MODE 定义如下:
// ISA signing authentication modes. Set ISA_SIGNING_AUTH_MODE to one
// of these to choose how ISAs are authenticated.
#define ISA_SIGNING_STRIP 1 // Strip the signature whenever reading an ISA.
#define ISA_SIGNING_AUTH 2 // Authenticate the signature on all ISAs.
// ISA signing modes. Set ISA_SIGNING_SIGN_MODE to one of these to
// choose how ISAs are signed.
#define ISA_SIGNING_SIGN_NONE 1 // Sign no ISAs.
#define ISA_SIGNING_SIGN_ONLY_SWIFT 2 // Only sign ISAs of Swift objects.
#define ISA_SIGNING_SIGN_ALL 3 // Sign all ISAs.
//ptrauth_objc_isa_strips || ptrauth_objc_isa_signs || ptrauth_objc_isa_authenticates
#if __has_feature(ptrauth_objc_isa_strips) || __has_feature(ptrauth_objc_isa_signs) || __has_feature(ptrauth_objc_isa_authenticates)
# if __has_feature(ptrauth_objc_isa_authenticates)
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH
# else
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP
# endif
# if __has_feature(ptrauth_objc_isa_signs)
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL
# else
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE
# endif
#else
# if __has_feature(ptrauth_objc_isa)
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL
# else
# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP
# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE
# endif
#endif
这篇文章分析了 objc_msgSend 的基本逻辑,下一篇讲详细分析 cache 查找流程。
[1]
Declared Properties: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17
[2]
runtime官方文档: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048
[3]
苹果官方文档网址: https://developer.apple.com/library/archive/navigation/
[4]
Objective-C Runtime Reference: https://developer.apple.com/documentation/objectivec/objective-c_runtime
[5]
Interacting with the Runtime: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtInteracting.html#//apple_ref/doc/uid/TP40008048-CH103-SW1
-End-