[TOC]
本篇英文名叫 Kitchen Tools That Cook Loves
,翻译过来的意思是苹果源码中出现的一些数据结构
,不断积累更新。
隶属于我的第二个大系列 CWC
:Cooking With Cook
,翻译过来的中文意思就是 作为一个长期热爱苹果的苹果开发者,陪着水果公司一起积累和成长。
目前:entsize_list_tt
、list_array_tt
、cache_t's buckets
…
1. entsize_list_tt
entsize_list_tt 其实就是一个通用的容器,可以获取 内部的迭代器,用于遍历内部存储的元素
- Element: 元素的类型
- List: 指定容器的具体实现类型
- uint32_t flagmask: flagmask 标记位
出现场景:
- 类的
ro
中的ivar_list_t
- 类的
ro
rw
rwe
中的property_array_t
中的property_list_t
- 类的
ro
rw
rwe
中的method_array_t
中的method_list_t
三者的声明头如下:
1 | struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> |
entsize_list_t 定义源码,省略大部分方法:
1 | /*********************************************************************** |
2. list_array_tt
这个类用来表示一个空、单数组、或者多数组。它和 list 的区别就是 多了一个多维数组的封装。
出现场景:
- 类的
rw
rwe
中的method_array_t
- 类的
rw
rwe
中的property_array_t
- 类的
rw
rwe
中的protocol_array_t
ro
中没有,只有三个单 List。
三者的声明头如下:
1 | class method_array_t : |
list_array_tt
源码部分如下:
1 | /*********************************************************************** |
3. cache_t 使用的 buckets
cache_t 的结构体定义:
定义类型 | 变量名 | 说明 |
---|---|---|
bucket_t | _buckets | 这段讲解的桶 |
mask_t | _mask | 其实就是个32无符号int uint32_t |
uint16_t | _flags | 一些位置标记 |
uint16_t | _occupied | 占位的数量,内部元素的数量 |
buckets 的内部是一个连续的存储空间,存储是一个散列表。
开辟声明的函数调用的是 calloc
当 msgSend 的时候,就会调用 fillCache 进行方法的缓存,存储的涉及 cls sel 和 imp
比较有意思的 bucket_t 内部的顺序定义
bucket_t 的结构体很有意思,arm64 和 i386 的两个值的顺序是反着的。
arm64
的时候是 :
定义类型 | 变量名 |
---|---|
explicit_atomic<uintptr_t> |
_imp |
explicit_atomic<SEL> |
_sel |
armv7*
,i386
和 x86_64
的时候是:
定义类型 | 变量名 |
---|---|
explicit_atomic<SEL> |
_sel |
explicit_atomic<uintptr_t> |
_imp |
源码注释:
1 | // IMP-first对arm64e的参数更好,而对arm64的效果更好。 |
散列表和哈希值的计算
1 | // 这里 hash 的计算是用 sel 指针和「capacity-1」做与操作计算 |
扩容:
初始的 capacity
是 4。
源码中 cache_t::insert(cls, sel, imp, reveiver)
方法调用的时候,判断扩容。
fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)
也就是说当大于四分之三的时候,就会进行扩容操作,每次 double 扩容
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
当然不是无限制的扩容,有一个最大容量的限制:
MAX_CACHE_SIZE = 1 << 16
DisguisedPtr
这个类型属于 objc-private.h
里面,是对于对象的一层包装,里面就存储一个对象而已。
出现的场景:就是关联对象的值用的就是这个包装的。
1 | // DisguisedPtr<T> 的作用类似于指针类型T*, |
NFY 占坑 explicit_atomic
这个类型应该是执行最多次的,看一些文章说一秒钟iOS中执行几百万次
explicit_atomic用来给catchT缓存方法用,核心是原子性和线程安全。
NFY 占坑 sideTable
weak弱引用的散列表
扩展
扩展:non-fragile structs 是什么?OC 1.0 (iOS自始至终都是2.0起的,Mac最开始是1.0)译器生成了一个 ivar 布局,显示了在类中从哪可以访问ivars
,对 ivar 的访问就可以通过 对象地址 + ivar偏移字节
的方法。苹果更新了NSObject类,例如增加一些属性,这个又是静态库,发布新版本的系统,这个时候布局就出错了,就不得不重新编译子类来恢复兼容性。(那如果是在线上运行的app,升级系统后就没办法运行了)
使用 Non Fragile ivars
时,程序进行检测来调整类中新增的 ivar 的偏移量。 这样就可以通过 对象地址 + 基类大小 + ivar偏移字节
的方法来计算出 ivar 相应的地址,并访问到相应的 ivar。(即使升级iOS系统,之前的app也能正常运行)
扩展再扩展:为什么OC类不能动态添加成员变量?runtime函数中,确实有一个class_addIvar()函数用于给类添加成员变量,但是文档中特别说明:This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用objc_registerClassPair之后才可以被使用,同样没有机会再添加成员变量。
理论上说,我还是认为可以添加,只是为什么一定不可以,就不得而知了。
References
- Post link: http://yangzai360.top/2020/11/10/CWC01_KitchenToolsThatCookLoves/
- Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.