[toc]
本文会分享 NSMutableDictionary 的创建过程和 cow 机制
NSMutableDictionary 的创建流程
本小节以下面的代码为例介绍 NSMutableDictionary
的创建过程
|
|
通过下面的指令,我们可以发现 NSMutableDictionary
类并不存在类方法 +[NSMutableDictionary dictionary]
|
|
所以,我们需要通过条件断点的方式添加合适的断点
|
|
添加断点后,我们会发现+[NSMutableDictionary dictionary]
会跳转到下面的汇编执行初始化任务:
|
|
该步骤与 __NSDictionaryI
类型,同样会依次执行以下三个任务:
- 通过
objc_alloc
申请一块区域,并初始化isa
等信息 - 通过
-[NSDictionary initWithObjects:forKeys:count:]
实例化 - 通过
objc_autorelease
将实例放到自动释放池()
指令复用
值得注意的是,因为 CoreFoundation
动态库存在很多对 objc_alloc
函数的调用。所以,很多可以复用的汇编指令片段会被提取到单个函数中。
以对 objc_alloc
的调用为例,汇编指令都被会提取并放到一个单独的函数: -[NSMutableArray replaceObject:].cold.1
。
受 arm64 固定指令长度影响,调用函数需要 3 个指令
可复用指令:
|
|
NSDate
创建时,同样存在对 -[NSMutableArray replaceObject:].cold.1
的调用
objc_alloc
与 __NSPlaceholderDictionary
的创建过程类似,可变字典同样会通过 objc_alloc
进行层层转发,并跳转到 +[NSDictionary allocWithZone:]
进行下一步处理。
+[NSDictionary allocWithZone:]
+[NSDictionary allocWithZone:]
会先判断 self
的类型
检测到 NSMutableDictionary
类型后,会调到 +160 行后进行安全检测,并调用 __NSDictionaryMutablePlaceholder
进行下一步处理
安全检测相关知识可以搜索关键字 clang stack_chk_guard stackprotector
__NSDictionaryMutablePlaceholder
与 __NSPlaceholderDictionary
的创建过程类似,__NSDictionaryMutablePlaceholder
同样会将 __NSPlaceholderDictionary
的实例赋值给 $x0
寄存器并返回
注意下面的地址
0x1f3d07178
,本文还会再次讲到
-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]
-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]
会根据情况对入参进行一系列的判断校验,比如 keys
是 NULL
并且 count
大于 0 时,会抛出异常
入参校验可以参考下面汇编代码的注释:
|
|
前面提到过地址 0x00000001d1c67178
。在实际运行中, -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]
会通过 self
的地址判断是否需要生成可变类型的实例
如果需要生成可变类型,会将 keys、objects、count、常量3
当做参数传给 __NSDictionaryM_new
函数
__NSDictionaryM_new
与不可变字典的处理类似,__NSDictionaryM_new
同样将 count
与 常量数组 __NSDictionaryCapacities
的值进行判断,并创建合适的实例
首先,我们先看看根据汇编反推到的字典内部结构
|
|
used
代表已经使用的空间大小,与开发者常用的count
属性对应mutbits
代表对字典变更的次数。初始化时是 1,增删会加 1szidx
通过搭配 常量数组__NSDictionarySizes
,获取字典的容量copyeKeys
: 代表需要复制 keybuffer
指向一块内存,该内存区域负责保存keys
和values
|
|
注意:-[NSSet intersectsOrderedSet:].cold.1
等价于调用 objc_opt_self
objc_autorelease
objc_autorelease
内部的逻辑比较简单:
-
如果
nil
,直接返回 -
如果是
TaggedPointer
,直接返回 -
如果类有自定义的
retain
或者release
方法,则通过调用[objc autorelease]
-
判断 x30 寄存器地址指向的指令是否等于
0xaa1d03fd
arm64
中,fd 03 1d aa
等价于mov fp, fp
-
如果等于,则将 1 存储到 tls
-
如果不等,则转发到
objc_object::rootAutorelease2()
进行下一步处理
|
|
总结
本文分享了很多通过 lldb 指令分析汇编和内存的小技巧,并对可变字典创建过程进行了逐步的分析,为下一篇分析 cow 机制打下了基础