objc_msgSend 实现分析

2021-07-21 17:18:47 浏览数 (1)

1. runtime运行时

由于缓存的读取和写入涉及到了 runtime 的知识,在这里做简单的介绍。

1.1 runtime概念

  • 编译时: 顾名思义就是正在编译的时候 , 那么什么叫做编译呢?就是编译器帮你把源代码翻译成机器能识别的代码。(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言)编译时就是简单的做一些翻译工作,词法分析,语法分析之类的过程。如果发现错误编译器就告诉你,这时的错误就叫编译时错误。这个过程中做的类型检查也就叫编译时类型检查或静态类型检查。(所谓静态就是没有真把代码放内存中运行起来,而只是把代码当作文本来扫描下)。
  • 运行时: 代码跑起来了被装载到内存中了(代码保存在磁盘上没装入内存之前是个死代码,只有跑到内 存中才变成活的)。运行时类型检查与编译时类型检查(静态类型检查)不一样,不是简单的扫描代码而是在内存中做些操作以及判断。比如:
代码语言:javascript复制
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 源码,编译器会创建实现动态语言特性的数据结构和函数调用。比如:

代码语言:javascript复制
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 类仅仅定了如何去做的模板,并不提供所有必须的实现。

NSProxy 底层也是 runtime 那一套,也是继承自 objc_object

代码语言:javascript复制
#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 的子类可以实现方法返回更多信息。例如,FoundationNSArray 返回它所包含对象列表的信息。

一些 NSObject 方法简单地查询 runtime system 的信息。这些方法允许对象进行自我检查。比如:

  • isKindOfClass:isMemberOfClass: 测试了类在继承链中的位置;
  • respondsToSelector: 对象是否能接受一个特定消息;
  • conformsToProtocol: 对象是否声明实现了一个特定协议中的方法;
  • methodForSelector: 提供了方法实现的地址。像上面这样的方法提供了对象内省的能力。
1.3.3 Runtime Functions

runtime 是一个提供了一套公共接口(函数和数据结构)的动态分享库,头文件在 /usr/include/objc。许多 Objective-C 代码可以通过 runtimeapi 替换为 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 的实现:

代码语言:javascript复制
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 调用方法,为了方便去掉参数调用:

代码语言:javascript复制
#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如下:

代码语言:javascript复制
- (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 结构如下:

代码语言:javascript复制
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
};

需要 receiversuper_classobjc2 下不需要 class

代码语言:javascript复制
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 的作用是什么呢?实现 HPObjctHPSubObject的resolveInstanceMethod 方法:

代码语言:javascript复制
  (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES;
}

调用一个不存在的方法 sayHello1:

代码语言:javascript复制
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 分为三种情况:nilobjsubObj 分别测试,结论如下:

  • receiver 有值
    • 值为 subObjsuper_class->resolveInstanceMethod --- receiver->resolveInstanceMethod --- _objc_terminate报错((NSobject)doesNotRecognizeSelector
    • 值为 objsuper_class->resolveInstanceMethod --- super_class->resolveInstanceMethod --- _objc_terminate报错((NSobject)doesNotRecognizeSelector)
  • receiver 没有值:super_class->resolveInstanceMethod --- (NSobject)doesNotRecognizeSelector报错

结论:父类处理不了的消息会发送给 receiverreceiver 相当于是个备胎。具体的逻辑会在后续的消息查找转发的时候分析。

方法的本质:消息接收者通过 sel 查找 imp 的过程

2. cache读取流程分析

上一篇文章中分析 cache 的调用时机是确认了如下调用顺序:__objc_msgSend_uncached(汇编)->lookUpImpOrForward->log_and_fill_cache->cls->cache.insert。最终是在 objc_msgSend_uncached 的汇编发起的调用。

objc-cache.mm 文件中有以下说明:

代码语言:javascript复制
 * 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_getImpobjc_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
  • GetTaggedClassGetClassFromIsa_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 POINTERnonpointer 是两个概念)。

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,其中 p13isa

代码语言:javascript复制
//获取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_auth1 时执行 ExtractISA(p16,isa,self)。(_objc_msgSend 传递的时候写死了 1
    • needs_auth0 时直接赋值 isap16。也就是 needs_auth0 的时候是纯指针。

伪代码实现:

代码语言: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 给到 p16iphone x 以后设备会有指针认证逻辑,这里有拿到真正地址逻辑,具体指令不是很熟悉。 ISA_SIGNING_AUTH_MODE 定义如下:

代码语言:javascript复制
// 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-

0 人点赞