RyukieDev
  • 关于我-AboutMe
  • 我的作品-MyApps
    • 「梦见」账本
      • 「梦见」账本(Umemi):极致的记账体验
      • 隐私协议:Privacy Policy
      • 服务协议:Terms of use
      • 外观预览:Preview
        • 赛博朋克-Cyberpunk
        • 樱-Sakura
        • 初恋-FirstLove
        • 永生-Eternal
        • 菲尼克斯-Phoenix
        • 报丧女妖-Banshee
        • 九霄-NYXL
        • Dream
        • 猕猴桃-Kiwi
        • 蜂蜜-Hachimitsu
        • DC
    • Elic-扫雷无尽天梯
    • 隐私访问记录
      • 03.如何分析iOS15隐私访问记录
      • PrivacyPolicy
      • FrameWorks
    • 醒词
      • PrivacyPolicy
      • TermsOfUse
    • 一色
      • PrivacyPolicy
    • 醒诗
      • PrivacyPolicy
    • 醒词键盘
      • PrivacyPolicy
    • 中文数字键盘
      • PrivacyPolicy
  • 独立开发
    • 产运
      • 01.没钱推广?这忘了这100美元
      • 02.在个人站点配置app-ads
      • 03.应用图标圆角
      • 04.iOS独立开发者注册公司到App备案上架.md
    • iCloud
      • 01.基于iCloud构建用户体系
      • 02.基于iCloud构建游戏内排行榜
  • Swift
    • 01.纯Swift路由方案探究
    • 02.使用Carthage替代CocoaPods
    • 03.逃逸闭包和非逃逸闭包
    • 04.向下向上取整
    • 05.Copy-on-write
    • 06.OC老项目Swift混编的一些坑
    • 07.OC项目中加入Swift混编
    • 08.Optional实质
    • 09.R-Swift-安全的资源组织方案forSwift
    • 10.Struct与Class
    • 11.Swift5新特性
    • 12.Swift性能分析
    • 13.SwiftPackage使用
    • 14.String与Substring
    • 15.Array,Set,Dictionary
    • 16.For-in跳跃遍历
    • 17.Switch元祖
    • 18.循环的标签控制
    • 19.Protocol与静态派发、动态派发
    • 20.Swift位移枚举
    • 21.Swift轻量级网络封装:SwiftyServiceProtocol(适用于混编或纯Swift项目)
    • 22.open与public
    • 23.Swift项目编译速度优化
    • 24.[译]编写高性能Swift代码-Writing High-Performance Swift Code(2022.8.25版)
    • 25.Swift编译流程
    • 26.Swift方法调度
  • SwiftUI
    • 01.Form
    • 02.Navigation
    • 03.ViewBuilder参数10个限制
    • 04.UIKit混编时Dismiss掉HostController
    • 05.如何在SwiftUI中使用ImagePicker?
    • 06.从some看Swift不透明类型
    • 07.TabView使用
    • 08.openURL
    • 09.Search
    • 10.SwifUI中使用WKWebView
  • DeepLearning
    • 基础知识
      • 01.感知机与神经网络
      • 02.线性可分
    • TensorFlow
      • 01.Anaconda
      • 02.JupyterNotebook
      • 03.安装TensorFlow
  • iOS
    • 底层
      • 01.alloc与字节对齐
      • 02.结构体内存对齐
      • 03.对象本质探究与isa
      • 04.ISA与Class
      • 05.深入探索Class的结构
      • 06.WWDC20-runtime优化
      • 07.深入探究属性
      • 08.isKindOfClass的底层实现
      • 09.slowpath和fastpath
      • 10.Class-cache_t
      • 11.源码解读objc_msgSend
      • 12.类的实现与初始化源码解读
      • 13.动态决议与消息转发
      • 14.iOS应用启动(一):dyld与main函数
      • 15.iOS应用启动(二):环境配置与runtime初始化
      • 16.iOS应用启动(三):镜像文件的读取和加载
      • 17.iOS应用启动(四):分类的加载
      • 18.关联对象源码解析
      • 19.MethodSwizzing方法交换的坑
      • 20.详解KVC
      • 21.KVO几个被忽视的细节
      • 22.KVO底层原理
      • 23.多线程原理与atomic
      • 24.任务与队列的几个面试题
      • 25.dispatch_once单例实现原理
      • 26.栅栏函数
      • 27.信号量
      • 28.锁|性能分析
      • 29.锁|@synchronized
      • 30.锁|递归锁
      • 31.锁|NSConditionLock
      • 32.关于Block你所该知道的一切
    • 内存管理
      • 01.从一个面试题看TaggedPointer
      • 02.Retain&Release
      • 03.SideTable和weak底层实现
      • 04.Timer优化
      • 05.自动释放池与Runloop
      • 06.dealloc
    • 编译器
      • 01.LLVM
    • 杂项
      • 01.堆栈的深度问题
      • 02.使用TTF字体
      • 03.为什么选VIPER
      • 04.项目路由方案
      • 05.隐藏导航栏下面的线
      • 06.源代码到IPA
      • 07.iOS重签名调研
      • 08.load与-initialize
      • 09.NSTimer与GCD
      • 10.NSURLConnection-和-NSURLSession
      • 11.Storyboard中UnwindSegue的使用
      • 12.UI调试-UIDebuggingInformationOverlay
      • 13.UIWebView和WKWebView
      • 14.UIWebView自适应高度
      • 15.weak实现原理
    • Runloop
      • 01.RunLoop
      • 02.autoreleasepool
    • Runtime
      • 01.基本操作
      • 02.实现NSCoding的自动归档和自动解档
      • 03.消息机制
      • 04.重写description打印对象信息
      • 05.MethodSwizzling的问题
    • 优化
      • 01.Apple官方资源瘦身方案ODR(一):初见
      • 02.Apple官方资源瘦身方案ODR(二):践行|换肤系统改造
      • 03.二进制重排实践
      • 04.iOS截屏防护方案
      • 05.提高编译速度
      • 06.图片格式-WebP
      • 07.App启动速度优化
      • 08.IDL自动化埋点
      • 09.渲染原理及优化
      • 10.「利用 Metrics 和 Diagnostics 提高性能」网络研讨活动
      • 11.离屏渲染
      • 12.一键搞定iOS16横竖屏切换
    • 多线程
      • 01.合适的线程数量
      • 02.死锁
      • 03.为什么用dispatch-once实现单例
      • 04.iOS多线程方案
      • 05.iOS多线程技术对比
    • Database
      • 01.数据库主键和外键
      • 02.FMDB-死锁问题
      • 03.FMDB与WCDB
      • 04.SQLite数据库修复
    • 架构
      • 01.组件化
  • 逆向
    • 01.寄存器
    • 03.iOS应用签名原理
    • 04.利用Xcode进行重签名与调试
    • 05.dylib注入
    • 06.MachO文件
    • 07.dyld
    • 08.Hook
    • 09.深入理解动态库与静态库
    • 10.通过符号表找到符号
    • 11.fishhook原理
    • 12.去符号与恢复符号
    • 13.反HOOK防护(一):基于Fishhook
    • 14.反HOOK防护(二):Monkey
    • 15.Inlinehook:Dobby
    • 16.LLDB
    • 17.虚拟内存
    • 18.Chisel工具
    • 19.DS.LLDB工具
    • 20.Cycript工具
    • 21.Cycrupt用法
    • 22.Logos
    • 23.应用砸壳
    • 24.实战人人视频破解
    • 25.解密被加密的数据库文件
  • Flutter
    • 01.初见Flutter
    • 02.Layout
    • 03.状态管理
    • 04.BottomNavigationBar
    • 05.MaterialApp
    • 06.android资源配置
    • 07.Positioned与Container嵌套无法充满容器
    • 08.Cell点击跳转
    • 09.代码规范
    • 10.通过联系人Cell看断言
    • 11.有状态Widget初始化重写&链式调用&排序
    • 12.索引条:手势及clamp函数
    • 13.ListView滑动到指定位置
    • 14.悬浮菜单列表
    • 15.Mock数据小技巧
    • 16.第三方库导入与网络数据异步请求与展示
    • 17.请求数据保留
    • 18.异步编程之Future
    • 19.Future&Microtask
    • 20.Dart异步编程:Isolates和事件循环
    • 21.Widget的生命周期
    • 22.Widget树&Render树&Element树
    • 23.Key
    • 24.调用原生相册
    • 25.iOS原生嵌入FlutterModule
  • 网络
    • 01 网络分层的优点
    • 02 网络理解
    • 03 iOS-网络安全之HTTPS
    • 04 POST和GET
    • 05 SSL-TLS四次握手
  • 直播技术
    • 01 直播技术相关
    • Socket-Little-Endian-Big-Endian
  • 知识点梳理
    • 01 面试算法题记录01
    • 02 面试题记录-C语言
    • 08 一套iOS底层试卷
    • 03 知识点梳理:iOS底层
    • 04 知识点梳理:网络
    • 05 知识点梳理:多线程
    • 06 知识点梳理:计算机基础
    • 07 知识点梳理:算法数据结构
    • 09 知识点梳理:HTML和浏览器
    • 10 知识点梳理:JavaSctipt
  • Framework
    • 01 CodeReading-01-YYModel
    • 02 RYImagePicker-iOS图片视频选择器
    • 03 RYImagesScroller-iOS高度自定义的图片轮播器
    • 04 RYPhotosBrowser
  • Issue
    • 01 使用KVC设置UIAlertAction按钮颜色的Crash
    • 02 iOS-常见崩溃分析
    • 03 UICollectionView的一些问题
  • OpenGL ES
    • 01.顶点着色器与片元着色器
  • 数据结构与算法
    • 剑指Offer-Swift
      • 03.找出数组中重复的数字
      • 04.二维数组中的查找
      • 05.替换空格
      • 06.从尾到头打印链表
      • 07.重建二叉树
      • 12.矩阵中的路径(回溯法)
      • 13.机器人的运动范围
      • 14.I.剪绳子
      • 14.II.剪绳子
      • 15.二进制中1的个数(含一个拓展问题)
      • 16.数值的整数次方
      • 18.删除链表的节点
      • 21.调整数组顺序使奇数位于偶数前面
      • 22.链表中倒数第k个节点
      • 24.反转链表
      • 25.合并两个排序的链表
      • 26.树的子结构
      • 27.二叉树的镜像
      • 28.对称的二叉树
      • 29.顺时针打印矩阵
      • 30.包含min函数的栈(容易被误导的一题)
      • 31.栈的压入、弹出序列
      • 32.I.从上到下打印二叉树
      • 32.II.从上到下打印二叉树II
      • 32.III.从上到下打印二叉树III
      • 32.从上到下花式打印二叉树
      • 33.二叉搜索树的后序遍历序列
      • 34.二叉树中和为某一值的路径
      • 35.复杂链表的复制(无Swift用例)
      • 36.二叉搜索树与双向链表
      • 37.序列化二叉树
      • 39.数组中出现次数超过一半的数字
      • 40.最小的k个数
      • 41.数据流中的中位数
      • 42.连续子数组的最大和
      • 43.1~n整数中1出现的次数
      • 44.数字序列中某一位的数字
      • 45.把数组排成最小的数
      • 46.把数字翻译成字符串
      • 47.礼物的最大价值
      • 48.最长不含重复字符的子字符串
      • 50.第一个只出现一次的字符
      • 52.两个链表的第一个公共节点
      • 53-I.在排序数组中查找数字
      • 53-II.0~n-1中缺失的数字
      • 54.二叉搜索树的第k大节点
      • 55-I.二叉树的深度
      • 55-II.平衡二叉树
      • 56-I.数组中数字出现的次数
      • 56-II.数组中数字出现的次数II
      • 57.和为s的两个数字
      • 58-I.翻转单词顺序
      • 58-II.左旋转字符串
      • 59-I.滑动窗口的最大值
      • 59-II.队列的最大值
      • 60.n个骰子的点数
      • 61.扑克牌中的顺子
      • 62.圆圈中最后剩下的数字
      • 63.股票的最大利润
      • 64.求1+2+…+n
      • 65.不用加减乘除做加法
      • 66.构建乘积数组
      • 67.把字符串转换成整数
      • 68-I.二叉搜索树的最近公共祖先
      • 68-II.二叉树的最近公共祖先
    • 技巧
      • 01.前缀和
      • 02.同余性质
      • 03.快速幂
      • 04.快速排序
      • 05.深度优先&广度优先
      • 06.冒泡排序
      • 07.摩尔投票
      • 08.优先队列
    • 数据结构
Powered by GitBook
On this page
  • 前言
  • 一、平时我们是如何调用方法的?
  • 二、汇编说明
  • ☕️ 休息一下吧,后面开始会有点累
  • 三 _objc_msgSend 源码汇编
  • ☕️ 看到这里,再休息一下吧。真机汇编看着更带劲儿~
  • 四、真机汇编调试
  • 🎉 感谢能耐心看到这里,不错不错
  • 五、未命中缓存的处理 __objc_msgSend_uncached
  • 六、动态决议与消息转发
  • 附:知识点补充

Was this helpful?

  1. iOS
  2. 底层

11.源码解读objc_msgSend

前言

  • 本文基于

    • objc4-818.2源码

    • iOS14.6 iPhoneX

  • 源码版本、系统版本、设备不同的话,真机调试的汇编会和源码有不同。自行调试的时候需要注意

  • 源码中对于重要部分做了注释,...表示省略了一些其他架构或者无关紧要的代码

  • 为了方便阅读,汇编代码中的做了缩进调整和分块注释,阅读起来应该会轻松不少

  • 电脑端阅读体验更佳哦

  • 本文看着会比较累,用了个看着比较轻松愉悦的主题。

    • 配上一杯 ☕️ 体验更佳

  • objc_msgSend 相关的内容很多,此文会持续更新、修正

    • 具体更新内容可以查看更新记录

欢迎收藏、交流、抓虫

更新记录

更新日期
变更

20210701

首次发布

一、平时我们是如何调用方法的?

有三种方式进行调用: OC Method

[model doSomething]

NSObject API

[model isKindOfClass:[NSString class]]

objc API。

class_getSuperclass([RYModel class])

API的层级结构如下:

1.1 实验三种方式

RYModel *obj = [[RYModel alloc] init];
[obj dosomethingA];

[obj performSelector:NSSelectorFromString(@"dosomethingA")];

// < Xcode 12
//    objc_msgSend((id)obj, sel_registerName("dosomethingA"));

// Xcode 12 需要转类型,只改Build setting不行
((void (*)(id, SEL))(void *)objc_msgSend)(obj, sel_registerName("dosomethingA"));

需要注意的点:

  • #import <objc/message.h>

  • Xcode 12 以下

    • Build setting -> objc_msgSend -> Enable Strict Checking of objc_msgSend Calls -> NO

  • Xcode 12 以上

    • 需要对 objc_msgSend 转类型,才能正常调用

输出:都能够正常调用

ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]

1.2 调用父类方法

@interface RYSubModel : RYModel

- (BOOL)isSub;

@end

@implementation RYSubModel

- (BOOL)isSub {
    NSLog(@"%s",__FUNCTION__);
    [super dosomethingA];
    return  YES;
}

@end

RYSubModel 继承 RYModel ,我们看看C++文件中 isSub 的情况。

static BOOL _I_RYSubModel_isSub(RYSubModel * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_RYModel_c52934_mi_6,__FUNCTION__);
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("RYSubModel"))}, sel_registerName("dosomethingA"));
    return ((bool)1);
}

这里 [super dosomethingA] 转成C++后,发现一个 objc_msgSendSuper 方法,通过它来调用父类的方法。

1.3 objc_msgSendSuper

我们试试能不能直接调用 objc_msgSendSuper。

  • objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

    • super

      • objc_super 结构体指针

    • op

      • selector

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

objc_super

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__ // 
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

代码

struct objc_super ry_super;
ry_super.receiver = obj;
ry_super.super_class = [RYModel class];
((void (*)(id, SEL))(void *)objc_msgSendSuper)((__bridge id)(&ry_super), sel_registerName("dosomethingA"));

输出:
ObjcMsgSend[17330:6265149] -[RYModel dosomethingA] // 调用父类方法成功

修改父类参数

我们将父类修改成它自己的类对象试试。 ry_super.super_class = [RYSubModel class];,输出依然正确。内部有个查找的链条。

1.4 源码

我们找一下 objc_msgSend 的源码。

ARM64版的源码都在 objc-msg-arm64.s 中,是 汇编 的。

通过添加符号断点也可以查看到汇编代码。

二、汇编说明

2.1 这里用到的主要的的汇编指令备注

指令
示例
备注

cmp

cmp x0, #0x0

作差值计算,结果丢弃,只调整状态寄存器CPSR标志位

b.le

b.le 0x1afad2b98;

意为less than or equal to,结果<=0,的时候跳转指令 0x1afad2b98

b.eq

b.eq 0x1afad2b68

意为equal,结果==0,时跳转指令0x1afad2b68

and

and x16, x13

x16 & x13 与操作

ldr

ldr x13, [x0]

将地址x0的储存器的数据,放入x13寄存器

lsr

lsr #48

逻辑右移48位

lsl

lsl #4

逻辑左移4位

ldp

ldp x17, x9, [x12]

取出x12的值存入x17,x9

b.ne

b.ne 0x1afad2b54

不相等,跳转指令0x1afad2b54

tbnz

tbnz p11, #0, LLookupPreopt\Function

Test and branch Not zero,即:如果p11的第0位不为0就执行LLookupPreopt\Function

TBZ

-

为0,和tbnz类似

adrp

adrp x0, 1

1. 将1的值,左移12位 1 0000 0000 0000 == 0x1000;\n 2.将PC寄存器的低12位清零 0x1045228b0 ==> 0x104522000; 3.将将1 和 2 的结果相加 给 X0 寄存器

eor

-

异或

2.2 寄存器说明

  • x0-x7,是用来传参的。

    • x0:消息接收者

    • x1:sel

☕️ 休息一下吧,后面开始会有点累

三 _objc_msgSend 源码汇编

ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame

// 判断是否存在消息接收者
cmp	p0, #0			// nil check and tagged pointer check 比较P0寄存器和0

#if SUPPORT_TAGGED_POINTERS 
// 支持Taggedpointer ARM64下 SUPPORT_TAGGED_POINTERS = 1,
// 根据状态寄存器结果 p0 - 0 <= 0 跳转执行 LNilOrTagged
b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
// 等于0,即接收者为nil,LReturnZero
b.eq	LReturnZero
#endif

// ISA 存入p13
ldr	p13, [x0]		// p13 = isa

// 从ISA中获取 Class,p16存的是Class的指针
GetClassFromIsa_p16 p13, 1, x0	// p16 = class

// 获取ISA完成后执行:
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 调用IMP或者查缓存
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

END_ENTRY _objc_msgSend

3.1 GetClassFromIsa_p16

获取Class存入p16

/********************************************************************
 * GetClassFromIsa_p16 src, needs_auth, auth_address
 * src is a raw isa field. Sets p16 to the corresponding class pointer.
 * The raw isa might be an indexed isa to be decoded, or a
 * packed isa that needs to be masked.
 *
 * On exit:
 *   src is unchanged
 *   p16 is a class pointer
 *   x10 is clobbered
 ********************************************************************/
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
...
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else // needs_auth == 1
	// 64-bit packed isa
	ExtractISA p16, \src, \auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro

3.2 ExtractISA 获取ClassISA

// XS/XS Max/XR A12处理器开始
#if __has_feature(ptrauth_calls)
// JOP
...
.macro ExtractISA
    // ISA($1) & ISA_MASK 存入 p16p16($0),即 class
	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
	movk	x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
	autda	$0, x10
#endif
.endmacro
...

// JOP
#else
// not JOP

...
.macro ExtractISA
    // ISA($1) & ISA_MASK 存入 p16($0),即 class
	and    $0, $1, #ISA_MASK
.endmacro

// not JOP
#endif

3.3 CacheLookup 查找缓存

参数:

  • Mode:缓存命中模式

    • CacheHit

  • Function

    • 命中缓存后调用

  • MissLabelDynamic

    • 未命中调用

  • MissLabelConstant

    • MissLabelConstant is only used for the GETIMP variant

官方注释

  • 建议查看一下,有助于理解

/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP <function> MissLabelDynamic MissLabelConstant
 *
 * MissLabelConstant is only used for the GETIMP variant.
 *
 * Locate the implementation for a selector in a class method cache.
 *
 * When this is used in a function that doesn't hold the runtime lock,
 * this represents the critical section that may access dead memory.
 * If the kernel causes one of these functions to go down the recovery
 * path, we pretend the lookup failed by jumping the JumpMiss branch.
 *
 * Takes:
 *	 x1 = selector
 *	 x16 = class to be searched
 *
 * Kills:
 * 	 x9,x10,x11,x12,x13,x15,x1
 *
 * Untouched:
 * 	 x14
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *                  In LOOKUP mode, the two low bits are set to 0x3
 *                  if we hit a constant cache (used in objc_trace)
 *          (not found) jumps to LCacheMiss
 *                  with x15 = class
 *                  For constant caches in LOOKUP mode, the low bit
 *                  of x16 is set to 0x1 to indicate we had to fallback.
 *          In addition, when LCacheMiss is __objc_msgSend_uncached or
 *          __objc_msgLookup_uncached, 0x2 will be set in x16
 *          to remember we took the slowpath.
 *          So the two low bits of x16 on exit mean:
 *            0: dynamic hit
 *            1: fallback to the parent class, when there is a preoptimized cache
 *            2: slowpath
 *            3: preoptimized cache hit
 *
 ********************************************************************/

源码

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	//
	// Restart protocol:
	//
	//   As soon as we're past the LLookupStart\Function label we may have
	//   loaded an invalid cache pointer or mask.
	//
	//   When task_restartable_ranges_synchronize() is called,
	//   (or when a signal hits us) before we're past LLookupEnd\Function,
	//   then our PC will be reset to LLookupRecover\Function which forcefully
	//   jumps to the cache-miss codepath which have the following
	//   requirements:
	//
	//   GETIMP:
	//     The cache-miss is just returning NULL (setting x0 to 0)
	//
	//   NORMAL and LOOKUP:
	//   - x0 contains the receiver
	//   - x1 contains the selector
	//   - x16 contains the isa
	//   - other registers are set as per calling conventions
	//

    // 将x16(class)移动到x15
	mov	x15, x16			// stash the original isa

LLookupStart\Function:
...


.endmacro

源码较长,我们分部分看

3.4 LLookupStart\Function 开始查找

  • 获取当前 bucket_t 和 HashIndex,用来查找缓存

    • 这里对源码的if-else缩进进行调整,方便查看

LLookupStart\Function:
	// p1 = _cmd, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
// ARM64 && __LP64__: MacOS 或 模拟器
...
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// ARM64 && __LP64__
    // ClassISA + #CACHE(0x10) 存入 p11,即cache_t(p11)
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
    #if CONFIG_USE_PREOPT_CACHES
        // 真机
        #if __has_feature(ptrauth_calls)
        // > X 的机型
            // 判断 cache_t(p11) 第0位是否不为0,如果为0,是否有共享缓存(first_shared_cache_sel)
            // 没缓存:继续走
            // 有缓存:跳转 LLookupPreopt\Function
            tbnz	p11, #0, LLookupPreopt\Function
            // cache_t(p11) & #0x0000ffffffffffff -> p10(buckets)
            // 取低 48 位
            and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
        #else
            and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
            tbnz	p11, #0, LLookupPreopt\Function
        #endif

        // (_cmd ^ _cmd >> 7) -> p12
        eor	p12, p1, p1, LSR #7
        // ((cache_t & p12) >> 48) -> p12(HashIndex)
        and	p12, p12, p11, LSR #48		// x12 = (_cmd(p1) ^ (_cmd(p1) >> 7)) & mask
    #else
        and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
        and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
    #endif // CONFIG_USE_PREOPT_CACHES

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// ARM64 && !__LP64__:真机
略
#else
#error Unsupported cache mask storage for ARM64.
#endif
    // ((buckets + HashIndex) << 1+PTRSHIFT) -> p13(bucket_t)
    // 通过偏移找到 bucket_t 存入p13
    // p12(HashIndex)
    // `buckets`是`buckets`的首地址,HashIndex为Hash表的下标
    // 如:HashIndex为1那就偏移1个`bucket_t`的长度,即:16(sel8+imp8)
    // 左移`(1+PTRSHIFT))`是为了到`HashIndex-1`位置,这里和cache_t插入缓存的逻辑保持一致,否则找不到的。
    // 此时p13存的的是目标bucket_t地址
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// #define PTRSHIFT 3  // 1<<PTRSHIFT == PTRSIZE

// 遍历缓存

// 第一次循环结束后

3.5 循环遍历(上)

						// do {
// #define BUCKET_SIZE      (2 * __SIZEOF_POINTER__) 即 (sel + imp 指针的大小)
// 取出x13 放入 p17(imp) p9(sel)
// 然后 `*bucket--` 向前移动2个指针的长度
// 即:一个bucket_t的长度
// *bucket--: 先取值,再--
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
    // 对比 p1(传入的sel) 和 p9(p13的sel)
	cmp	p9, p1				//     if (sel != _cmd) {
	b.ne	3f				//         scan more
						//     } else {
// 命中调用 CacheHit \Mode
2:	CacheHit \Mode				// hit:    call or return imp
						//     }
3:	cbz	p9, \MissLabelDynamic		//     if (sel == 0) goto Miss;
	cmp	p13, p10			// } while (bucket >= buckets)
    // 未命中 回到 1 继续循环
	b.hs	1b

3.6 CacheHit

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
    // 调用缓存IMP
	TailCallCachedImp x17, x10, x1, x16	// authenticate and call imp
.elseif $0 == GETIMP
	mov	p0, p17
	cbz	p0, 9f			// don't ptrauth a nil imp
	AuthAndResignAsIMP x0, x10, x1, x16	// authenticate imp and re-sign as IMP
9:	ret				// return IMP
.elseif $0 == LOOKUP
	// No nil check for ptrauth: the caller would crash anyway when they
	// jump to a nil IMP. We don't care if that jump also fails ptrauth.
	AuthAndResignAsIMP x17, x10, x1, x16	// authenticate imp and re-sign as IMP
	cmp	x16, x15
	cinc	x16, x16, ne			// x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro

3.7 TailCallCachedImp 执行缓存IMP

#if __has_feature(ptrauth_calls)
// JOP
...

.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // (addressOfCachedIMP ^ SEL) -> addressOfCachedIMP
	eor	$1, $1, $2	// mix SEL into ptrauth modifier
    // (addressOfCachedIMP ^ isa) -> addressOfCachedIMP
	eor	$1, $1, $3  // mix isa into ptrauth modifier
    // 这个指令还没搞明白
	brab	$0, $1
.endmacro

// JOP
#else
// not JOP
...

.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // (cachedIMP ^ isa) -> cachedIMP
	eor	$0, $0, $3
    // 跳转执行 cachedIMP
	br	$0
.endmacro

...

// not JOP
#endif

3.8 循环遍历(下)

  • 当循环回到第一个bucket的时候就停止。

  • 将p13移到 buckets + (mask << 1+PTRSHIFT)

  • 继续遍历,直到遍历完

  • 如果仍然没有命中,_objc_msgSend_uncached

// wrap-around:
//   p10 = first bucket
//   p11 = mask (and maybe other bits on LP64)
//   p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.

...
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits
...

	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	cmp	p9, p1				//     if (sel == _cmd)
	b.eq	2b				//         goto hit
	cmp	p9, #0				// } while (sel != 0 &&
	ccmp	p13, p12, #0, ne		//     bucket > first_probed)
	b.hi	4b

3.9 LLookupPreopt\Function: 查找共享缓存内的数据

#if CONFIG_USE_PREOPT_CACHES
...
    LLookupPreopt\Function:
    #if __has_feature(ptrauth_calls)
        // cache_t(p11) & #0x007ffffffffffffe 存入 p10
        and	p10, p11, #0x007ffffffffffffe	// p10 = buckets
        // 验证?没找到这个指令的具体作用
        autdb	x10, x16			// auth as early as possible
    #endif
        // 查找共享缓存内的数据
        // x12 = (_cmd - first_shared_cache_sel)
        adrp	x9, _MagicSelRef@PAGE
        ldr	p9, [x9, _MagicSelRef@PAGEOFF]
        sub	p12, p1, p9

        // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
    #if __has_feature(ptrauth_calls)
        // bits 63..60 of x11 are the number of bits in hash_mask
        // bits 59..55 of x11 is hash_shift

        lsr	x17, x11, #55			// w17 = (hash_shift, ...)
        lsr	w9, w12, w17			// >>= shift

        lsr	x17, x11, #60			// w17 = mask_bits
        mov	x11, #0x7fff
        lsr	x11, x11, x17			// p11 = mask (0x7fff >> mask_bits)
        and	x9, x9, x11			// &= mask
    #else
        // bits 63..53 of x11 is hash_mask
        // bits 52..48 of x11 is hash_shift
        lsr	x17, x11, #48			// w17 = (hash_shift, hash_mask)
        lsr	w9, w12, w17			// >>= shift
        and	x9, x9, x11, LSR #53		// &=  mask
    #endif

        ldr	x17, [x10, x9, LSL #3]		// x17 == sel_offs | (imp_offs << 32)
        cmp	x12, w17, uxtw

    .if \Mode == GETIMP
    ...
    .else
        b.ne    5f                // cache miss
        sub    x17, x16, x17, LSR #32        // imp = isa - imp_offs

    .if \Mode == NORMAL
        br	x17
    .elseif \Mode == LOOKUP
    ...

    5:	ldursw	x9, [x10, #-8]			// offset -8 is the fallback offset
        add	x16, x16, x9			// compute the fallback isa
        b	LLookupStart\Function		// lookup again with a new isa
    .endif
#endif // CONFIG_USE_PREOPT_CACHES

3.10 GetTaggedClass

  • if SUPPORT_TAGGED_POINTERS

// Look up the class for a tagged pointer in x0, placing it in x16.
.macro GetTaggedClass

	and	x10, x0, #0x7		// x10 = small tag
	asr	x11, x0, #55		// x11 = large tag with 1s filling the top (because bit 63 is 1 on a tagged pointer)
	cmp	x10, #7		// tag == 7?
	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, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ldr	x16, [x10, x12, LSL #3]

.endmacro

3.11 LNilOrTagged

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq	LReturnZero		// nil check
GetTaggedClass
b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

3.12 LReturnZero

LReturnZero:
// x0 is already zero
mov	x1, #0
movi	d0, #0
movi	d1, #0
movi	d2, #0
movi	d3, #0
ret

☕️ 看到这里,再休息一下吧。真机汇编看着更带劲儿~

四、真机汇编调试

个人觉得看符号断点的汇编更直观,但是源码中有注释。

真机符号断点的作为参考,和源码对照着再看一遍会更加清晰。

libobjc.A.dylib`objc_msgSend:
/*** Start
判断接收者是否为空 
***/
->  0x197b05140 <+0>:   cmp    x0, #0x0                  ; =0x0 
    0x197b05144 <+4>:   b.le   0x197b051e8               ; <+168>
/***************************/

/*** Start
将接收者存入 x13 
即:x13 = isa 
***/
    0x197b05148 <+8>:   ldr    x13, [x0]
/***************************/

/*** Start
GetClassFromIsa_p16 -> 
ExtractISA: 
isa & ISA_MASK -> x16
即:x16 = class的isa 
***/
    0x197b0514c <+12>:  and    x16, x13, #0xffffffff8
/***************************/

/*** Start
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
查找缓存
***/
    0x197b05150 <+16>:  mov    x15, x16 // LGetIsaDone

    /*** Start
    LLookupStart\Function:
    ClassISA + #CACHE(0x10) 存入 p11,即cache_t(p11)
    判断 cache_t 第0位是否不为0,如果为0,是否有共享缓存。有就跳转 LLookupPreopt
    ***/
    0x197b05154 <+20>:  ldr    x11, [x16, #0x10]
    0x197b05158 <+24>:  and    x10, x11, #0xfffffffffffe
    0x197b0515c <+28>:  tbnz   w11, #0x0, 0x197b051b0    ; <+112>

        /*** Start
        计算 cache_hash 
        (sel ^ sel >> 7) -> p12
        ((cache_t & p12) >> 48) -> p12(HashIndex)
        ***/
        0x197b05160 <+32>:  eor    x12, x1, x1, lsr #7
        0x197b05164 <+36>:  and    x12, x12, x11, lsr #48
        /***************************/

    /***************************/

    /*** Start
    通过偏移找到 bucket_t 存入x13
    x12(HashIndex)
    `buckets`是`buckets`的首地址,HashIndex为Hash表的下标
    如:HashIndex为1那就偏移1个`bucket_t`的长度,即:16(sel8+imp8)
    左移`(1+PTRSHIFT))`是为了到`HashIndex-1`位置,这里和cache_t插入缓存的逻辑保持一致,否则找不到的。
    (buckets + HashIndex << 1+PTRSHIFT) -> p13(bucket_t)
    此时x13存的的是目标bucket_t地址
    ***/
    0x197b05168 <+40>:  add    x13, x10, x12, lsl #4
    /***************************/

    /*** Start
    遍历查缓存
    #define BUCKET_SIZE      (2 * __SIZEOF_POINTER__) 即 (sel + imp 指针的大小)
    取出 x13 - 0x10 放入 p17(imp) p9(sel)
    0x10 = 2,即2个单位
    `*bucket--` 向前移动2个指针的长度(sel + imp)
    即:前移一个bucket_t的长度,将x13指向上一个缓存
    对比缓存和_cmd
    ***/
    0x197b0516c <+44>:  ldp    x17, x9, [x13], #-0x10
    0x197b05170 <+48>:  cmp    x9, x1
    0x197b05174 <+52>:  b.ne   0x197b05180               ; <+64>

        /*** Start
        命中缓存 TailCallCachedImp
        (cachedIMP ^ isa) -> cachedIMP
        ***/ 
        0x197b05178 <+56>:  eor    x17, x17, x16
        0x197b0517c <+60>:  br     x17
        /***************************/

    /*** Start
    while 循环条件判断
    if (sel(x9) == 0) goto Miss;
    while (bucket >= buckets) 满足条件回到 +44 指令行继续循环
    ***/
    0x197b05180 <+64>:  cbz    x9, 0x197b054c0           ; _objc_msgSend_uncached
    0x197b05184 <+68>:  cmp    x13, x10// 
    0x197b05188 <+72>:  b.hs   0x197b0516c               ; <+44>
    /***************************/


    /*** Start
    当循环回到第一个bucket的时候就停止循环后:
    
    * 将p13移到 buckets + (mask << 1+PTRSHIFT)
    * 继续遍历,直到遍历完
    * 如果仍然没有命中,_objc_msgSend_uncached
    ***/
    0x197b0518c <+76>:  add    x13, x10, x11, lsr #44
    0x197b05190 <+80>:  add    x12, x10, x12, lsl #4

        /*** Start
        第二次遍历
        ***/
        0x197b05194 <+84>:  ldp    x17, x9, [x13], #-0x10

            /*** Start
            命中缓存 跳转 56指令行:TailCallCachedImp
            ***/ 
            0x197b05198 <+88>:  cmp    x9, x1
            0x197b0519c <+92>:  b.eq   0x197b05178               ; <+56>
            /***************************/
        
        // 循环条件判断
        0x197b051a0 <+96>:  cmp    x9, #0x0                  ; =0x0 
        0x197b051a4 <+100>: ccmp   x13, x12, #0x0, ne
        //     bucket > first_probed)
        0x197b051a8 <+104>: b.hi   0x197b05194               ; <+84>
        /***************************/

        0x197b051ac <+108>: b      0x197b054c0               ; _objc_msgSend_uncached
    /***************************/

    /*** Start
    LLookupPreopt\Function
    查找共享缓存内的数据
    ***/

    // x12 = (_cmd - first_shared_cache_sel)
    0x197b051b0 <+112>: adrp   x9, 227000
    0x197b051b4 <+116>: add    x9, x9, #0x47b            ; =0x47b 
    0x197b051b8 <+120>: sub    x12, x1, x9

    // bits 63..53 of x11 is hash_mask
    // bits 52..48 of x11 is hash_shift
    0x197b051bc <+124>: lsr    x17, x11, #48
    0x197b051c0 <+128>: lsr    w9, w12, w17
    0x197b051c4 <+132>: and    x9, x9, x11, lsr #53

    // x17 == sel_offs | (imp_offs << 32)
    0x197b051c8 <+136>: ldr    x17, [x10, x9, lsl #3]
    0x197b051cc <+140>: cmp    x12, w17, uxtw

    0x197b051d0 <+144>: b.ne   0x197b051dc               ; <+156>
    0x197b051d4 <+148>: sub    x17, x16, x17, lsr #32

    0x197b051d8 <+152>: br     x17

        /*** Start
        5.
        ***/
        // offset -8 is the fallback offse
        0x197b051dc <+156>: ldursw x9, [x10, #-0x8]
        // compute the fallback isa
        0x197b051e0 <+160>: add    x16, x16, x9
        // lookup again with a new isa
        0x197b051e4 <+164>: b      0x197b05154               ; <+20>
        /***************************/

    /***************************/


    /*** Start
    接收者为空时调用 LNilOrTagged
    ***/
    0x197b051e8 <+168>: b.eq   0x197b0520c               ; <+204>

        /*** Start
        GetTaggedClass
        ***/
        0x197b051ec <+172>: and    x10, x0, #0x7
        0x197b051f0 <+176>: asr    x11, x0, #55
        0x197b051f4 <+180>: cmp    x10, #0x7                 ; =0x7 
        0x197b051f8 <+184>: csel   x12, x11, x10, eq
        0x197b051fc <+188>: adrp   x10, 255422
        0x197b05200 <+192>: add    x10, x10, #0xb60          ; =0xb60 
        0x197b05204 <+196>: ldr    x16, [x10, x12, lsl #3]
        /***************************/

    0x197b05208 <+200>: b      0x197b05150               ; <+16> // LGetIsaDone
    /***************************/

    /*** Start
    LReturnZero
    ***/
    0x197b0520c <+204>: mov    x1, #0x0
    0x197b05210 <+208>: movi   d0, #0000000000000000
    0x197b05214 <+212>: movi   d1, #0000000000000000
    0x197b05218 <+216>: movi   d2, #0000000000000000
    0x197b0521c <+220>: movi   d3, #0000000000000000
    0x197b05220 <+224>: ret    
    /***************************/   

🎉 感谢能耐心看到这里,不错不错

  • 看汇编是真的费劲儿

objc_msgSend 汇编流程图

五、未命中缓存的处理 __objc_msgSend_uncached

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class

// r10 is already the class to search
MethodTableLookup NORMAL	// r11 = IMP
jmp	*%r11			// goto *imp

END_ENTRY __objc_msgSend_uncached

这里会调用 MethodTableLookup。

5.1 查找方法列表 MethodTableLookup

.macro MethodTableLookup
	// 创建一个栈,为调用方法做准备,存储寄存器中的值
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3

	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

    // 复原 SAVE_REGS用到的寄存器,出栈
	RESTORE_REGS MSGSEND

.endmacro

5.2 慢速查找 _lookUpImpOrForward

参数
说明

id inst

receiver(x0)

SEL sel

selector(x1)

Class cls

ClassISA

int behavior

method lookup

behavior: LOOKUP_INITIALIZE | LOOKUP_RESOLVER)

流程图

源码解析

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    /**
    发给Class的第一个消息通常是通过了objc_opt_*或各种优化的入口:
    + new
    + alloc
    + self
    但是,此时Class尚未初始化
    */
    if (slowpath(!cls->isInitialized())) { 
        behavior |= LOOKUP_NOCACHE;
     }

    /** 
    加锁
    保证 method-lookup + cache-fill 的原子性。
    否则
    可能分类添加后会被永久忽略,因为缓存被旧的数据覆盖
    */
    runtimeLock.lock();

    /**
    为防止 CFI攻击
    检查是否是已注册的类
    确保Class编译进了二进制文件
    或者是使用 
    objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair
    合法的注册过的
    */
    checkIsKnownClass(cls);

    // 如果需要的话初始化class
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);

    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    /**
    死循环。通过goto和break跳出
    */
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
            ...
        } else {
            // curClass method list.
            // 用二分法查找sel
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            // 这里将获取了父类并赋值给 curClass
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // 父类都找完了(superClass == nil)也没有就进行消息转发了
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }
        ...

        // Superclass cache. 这里的 curClass 已经是父类了
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // 需要进行消息转发
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    ...

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
    // iOS真机
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        // 插入缓存
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
...
}

5.3 二分查找赏析

这里的算法比较精妙,值得学习。

/***********************************************************************
 * search_method_list_inline
 **********************************************************************/
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin();
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    /**
    count >>= 1,右移一位,即二分一次
    */
    for (count = list->count; count != 0; count >>= 1) {
        // 居中的下标
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            /**
            probe > first
            开始遍历一直把同名的遍历完,因为有分类的存在,所以可能会有同名的。
            会掉分类的。
            */
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

六、动态决议与消息转发

附:知识点补充

1. 取 buckets 过程的补充说明

// 判断 cache_t(p11) 第0位是否不为0,如果为0,是否有共享缓存(first_shared_cache_sel)
// 没缓存:继续走
// 有缓存:跳转 LLookupPreopt\Function
tbnz	p11, #0, LLookupPreopt\Function
// cache_t(p11) & #0x0000ffffffffffff -> p10(buckets)
// 取低 48 位
and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
  • 这里的第0位,是指低位的0位

2. OBJC2

我们在进行源码学习的时候时常会见到__OBJC2__这个宏定义。

  • 因为runtime有两个版本

    • 1.0:用于早期系统上,32位的MacOS上

    • 2.0(现行版本):iOS和MacOS X 10.5以后的64位系统上

  • 想要了解更多可以查看下面的官方文档:

3. cache_t

4. Class的实现(Realize)和初始化(Initialize)

Previous10.Class-cache_tNext12.类的实现与初始化源码解读

Last updated 2 years ago

Was this helpful?

msgsend01.png

寄存器
动态决议与消息转发
Objective-C Runtime Programming Guide
Objective-C Runtime
【Lawliet玩iOS源码】Class: cache_t
类的实现与初始化
runtimeLevel.png
objc_msgSend汇编流程图.png
慢速查找