美文网首页IOS开发
objc_msgSend实现分析

objc_msgSend实现分析

作者: HotPotCat | 来源:发表于2021-06-27 15:27 被阅读0次

一、 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

⚠️ runtime就是c/c++/汇编写的一套API

runtime官方文档
对于苹果的一些文档资料都可以在这里搜索:苹果官方文档网址,不过苹果现在不怎么维护文档了。

1.3 runtime的发起方式

Objective-C程序有三种途径和运行时系统交互:

  • 通过Objective-C 代码,比如:[obj method]
  • 通过Foundation框架中NSObject的方法,比如:isKindofClass
  • 通过调用运行时系统提供的API接口,比如:class_getInstanceSize

对应的结构图如下:


image.png

最终会通过编译器与运行时系统交互。

1.3.1 Objective-C Source Code

大多数情况下,运行时系统自动工作。我们仅仅需要编写和编译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"));

1.3.2 NSObject Methods

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的子类可以实现方法返回更多信息。例如,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

Interacting with the Runtime

1.4 clang还原底层代码

有如下代码:

#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 + 参数))

1.4.1 objc_msgSend

那么就意味着我们自己也可以通过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个)。意味着不让编译器检查。

image.png

1.4.2 objc_msgSendSuper

添加一个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
};

需要receiversuper_classobjc2下不需要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的作用是什么呢?
实现HPObjctHPSubObjectresolveInstanceMethod方法:

+ (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分为三种情况: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的过程

二、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_getImpobjc_msgSend相关的。(写入逻辑肯定是在读取逻辑之后的,先判断有没有缓存,没有的话才去查找再存)所以要研究写入就要先搞清楚是怎么读取的。

image.png
搜索后发现cache_getImp的实现是在汇编中的,本片文章以arm64架构进行分析。

汇编代码函数名需要比调用多一个_,这个是格式规定。

三、 objc_msgSend源码解析

objc_msgSend打断点发现是libobjc.A.dylib库的:

image.png
汇编相关的指令可以参考我之前的文章汇编-循环、选择、判断

3.1 _objc_msgSend

源码解读如下:

    //入口
    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清空寄存器的值然后返回。

伪代码实现:

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

// 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
}

3.2.2 GetClassFromIsa_p16

_objc_msgSend的调用中代码为:GetClassFromIsa_p16 p13, 1, x0,其中p13isa

//获取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的时候是纯指针。

伪代码实现:

//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给到p16iphone 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查找流程。

arm64指令的一些参考
https://www.jianshu.com/p/99067af33f14
https://www.cnblogs.com/rongmouzhang/p/9707516.html
http://news.eeworld.com.cn/mcu/2015/0930/article_22693.html
https://blog.csdn.net/chengbeng1745/article/details/111473502
https://blog.csdn.net/Decisiveness/article/details/46312607

相关文章

网友评论

    本文标题:objc_msgSend实现分析

    本文链接:https://www.haomeiwen.com/subject/rapgyltx.html