1. runtime运行时
由于缓存的读取和写入涉及到了 runtime
的知识,在这里做简单的介绍。
1.1 runtime概念
- 编译时: 顾名思义就是正在编译的时候 , 那么什么叫做编译呢?就是编译器帮你把源代码翻译成机器能识别的代码。(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言)编译时就是简单的做一些翻译工作,词法分析,语法分析之类的过程。如果发现错误编译器就告诉你,这时的错误就叫编译时错误。这个过程中做的类型检查也就叫编译时类型检查或静态类型检查。(所谓静态就是没有真把代码放内存中运行起来,而只是把代码当作文本来扫描下)。
- 运行时: 代码跑起来了被装载到内存中了(代码保存在磁盘上没装入内存之前是个死代码,只有跑到内 存中才变成活的)。运行时类型检查与编译时类型检查(静态类型检查)不一样,不是简单的扫描代码而是在内存中做些操作以及判断。比如:
id obj = [NSObject alloc]
[xxx performSelector:@selector(xxx)]
多态和运行时就是 runtime
的东西。
1.2 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],不过苹果现在不怎么维护文档了。
1.3 runtime的发起方式
Objective-C
程序有三种途径和运行时系统交互:
- 通过
Objective-C
代码,比如:[obj method]
。 - 通过
Foundation
框架中NSObject
的方法,比如:isKindofClass
。 - 通过调用运行时系统提供的
API
接口,比如:class_getInstanceSize
。
对应的结构图如下:
最终会通过编译器与运行时系统交互。
1.3.1 Objective-C Source Code
大多数情况下,运行时系统自动工作。我们仅仅需要编写和编译 Objective-C
源码,编译器会创建实现动态语言特性的数据结构和函数调用。比如:
HPObject *obj = [HPObject alloc];
[obj sayHello];
编译器编译后就会变成:
代码语言:javascript复制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"));
1.3.2 NSObject Methods
在 Cocoa
中的大多数对象是 NSObject
的子类( NSProxy
除外),大多数类继承了它所定义的方法。在少数情况下 NSObject
类仅仅定了如何去做的模板,并不提供所有必须的实现。
代码语言:javascript复制
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:
提供了方法实现的地址。像上面这样的方法提供了对象内省的能力。
1.3.3 Runtime Functions
runtime
是一个提供了一套公共接口(函数和数据结构)的动态分享库,头文件在 /usr/include/objc
。许多 Objective-C
代码可以通过 runtime
的 api
替换为 C
实现。文档: Objective-C Runtime Reference[4]。
Interacting with the Runtime[5]
1.4 clang 还原底层代码
有如下代码:
代码语言:javascript复制#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)
加个参数再看下编译后的代码:
代码语言:javascript复制//编译前:
[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 参数))
1.4.1 objc_msgSend
那么就意味着我们自己也可以通过 objc_msgSend
调用方法,为了方便去掉参数调用:
#import <objc/message.h>
[obj sayHello];
objc_msgSend(obj,@selector(sayHello));
objc_msgSend(obj,sel_registerName("sayHello"));
输出:
代码语言:javascript复制-[HPObject sayHello]
-[HPObject sayHello]
-[HPObject sayHello]
三种方式调用效果相同。
需要配置让 objc_msgSend
支持多个参数。build setting-> Enable Strict Checking of objc_msg_send Calls
设置为 No
,默认值为 Yes
(只接收1个)。意味着不让编译器检查。
1.4.2 objc_msgSendSuper
添加一个 HPObject
的子类 HPSubObject
,重写 sayHello
如下:
- (void)sayHello {
[super sayHello];
NSLog(@"%s",__func__);
}
编译后代码如下:
代码语言:javascript复制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声明如下:
代码语言:javascript复制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"));
输出:
代码语言:javascript复制-[HPObject sayHello]
-[HPObject sayHello]
-[HPObject sayHello]//重新方法中调用的super
-[HPSubObject sayHello]
super_class
为第一要查找的类,也就是开始从哪一层开始查找,找不到找父类继续查找。receiver
消息接收者。
再修改下代码:
代码语言:javascript复制// 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"));
输出:
代码语言:javascript复制-[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)doesNotRecognizeSelector
- 值为
obj
:super_class->resolveInstanceMethod --- super_class->resolveInstanceMethod --- _objc_terminate报错((NSobject)doesNotRecognizeSelector)
- 值为
receiver
没有值:super_class->resolveInstanceMethod --- (NSobject)doesNotRecognizeSelector报错
结论:父类处理不了的消息会发送给 receiver
。receiver
相当于是个备胎。具体的逻辑会在后续的消息查找转发的时候分析。
方法的本质:消息接收者通过 sel
查找 imp
的过程。
2. cache读取流程分析
上一篇文章中分析 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
架构进行分析。
汇编代码函数名需要比调用多一个_,这个是格式规定。
3. objc_msgSend源码解析
objc_msgSend
打断点发现是 libobjc.A.dylib
库的:
汇编相关的指令可以参考我之前的文章汇编-循环、选择、判断
3.1 _objc_msgSend
源码解读如下:
代码语言:javascript复制 //入口
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
清空寄存器的值然后返回。
伪代码实现:
代码语言:javascript复制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)
}
3.2 通过isa->cls
通过 isa
获取 cls
分为两种,一种是 TAGGED POINTER
,一种是普通的指针(有可能不是纯 isa
,有可能是 nonpointer
。这里 TAGGED POINTER
与 nonpointer
是两个概念)。
3.2.1 GetTaggedClass
代码语言:javascript复制// 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
伪代码实现:
代码语言:javascript复制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
}
3.2.2 GetClassFromIsa_p16
在 _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
。- 32位:
- 开启
nonpointer
,会根据indexcls
找到cls
。 - 没有开启
nonpointer
,直接cls
赋值给p16
- 开启
- 64位:
needs_auth
为1
时执行ExtractISA(p16,isa,self)
。(_objc_msgSend
传递的时候写死了1
)needs_auth
为0
时直接赋值isa
给p16
。也就是needs_auth
为0
的时候是纯指针。
伪代码实现:
代码语言:javascript复制//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
代码语言:javascript复制//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-