Apple 源码用到的一些数据结构

本篇英文名叫 CWC:Kitchen Tools That Cook Loves,翻译过来的意思是苹果源码中出现的一些数据结构,不断积累更新。
CWCCooking With Cook,翻译过来的中文意思就是 作为一个长期热爱苹果的苹果开发者,我们要陪着水果公司一起积累和成长。

目前:entsize_list_ttlist_array_ttcache_t's buckets...

1. entsize_list_tt

entsize_list_tt 其实就是一个通用的容器,可以获取 内部的迭代器,用于遍历内部存储的元素

  • Element: 元素的类型
  • List: 指定容器的具体实现类型
  • uint32_t flagmask: flagmask 标记位

出现场景:

  1. 类的 ro 中的 ivar_list_t
  2. 类的 ro rw rwe 中的 property_array_t 中的 property_list_t
  3. 类的 ro rw rwe 中的 method_array_t 中的 method_list_t

三者的声明头如下:

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0>
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0>
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3>

entsize_list_t 定义源码,省略大部分方法:

/***********************************************************************
* entsize_list_tt<Element, List, FlagMask>
* 一系列非精致结构体的数组的实现
*
* Element 元素是结构体类型 (e.g. method_t)
* List 是这个结构体的特殊实现子类 (e.g. method_list_t)
* FFlagMask 用于在 entsize 字段中存储额外的位
*   (e.g. method list fixup markers)
**********************************************************************/
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
    struct iterator;

    struct iterator {
        uint32_t entsize;
        uint32_t index;  // keeping track of this saves a divide in operator-
        Element* element;

        typedef std::random_access_iterator_tag iterator_category;
        typedef Element value_type;
        typedef ptrdiff_t difference_type;
        typedef Element* pointer;
        typedef Element& reference;
    }
}

2. list_array_tt

这个类用来表示一个空、单数组、或者多数组。它和 list 的区别就是 多了一个多维数组的封装。

出现场景:

  1. 类的 rw rwe 中的 method_array_t
  2. 类的 rw rwe 中的 property_array_t
  3. 类的 rw rwe 中的 protocol_array_t

ro 中没有,只有三个单 List。

三者的声明头如下:

class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
    
class property_array_t : 
    public list_array_tt<property_t, property_list_t> 
    
class protocol_array_t : 
    public list_array_tt<protocol_ref_t, protocol_list_t> 

list_array_tt 源码部分如下:

/***********************************************************************
* list_array_tt<Element, List>
* 元数据的通用实现,可以按类别进行扩展。
*
* Element:基础元数据类型(e.g. method_t)
* List: 元数据的列表类型(e.g. method_list_t)
*
*  list_array_tt具有以下三个值之一:
* - 空
* - 指向单个列表的指针
* - 指向多个列表的指针的数组
*
* countLists/beginLists/endLists 迭代元数据
* count/begin/end 迭代基础元数据元素
**********************************************************************/
template <typename Element, typename List>
class list_array_tt {
    struct array_t {
        uint32_t count;
        List* lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
}

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*i386x86_64 的时候是:

定义类型 变量名
explicit_atomic<SEL> _sel
explicit_atomic<uintptr_t> _imp

源码注释:

    // IMP-first对arm64e的参数更好,而对arm64的效果更好。
    // 对于armv7*,i386和x86_64,SEL-first更好。

散列表和哈希值的计算

// 这里 hash 的计算是用 sel 指针和「capacity-1」做与操作计算
// 下面的 do-while 去处理 hash 冲突的寻址。
// 这里解决冲突的方法不是顺序+1,
// 而是使用这个方法再次计算hash值
// 「fastpath((i = cache_next(i, m)) != begin)」
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);

扩容:

初始的 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

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

  1. objective-c non-fragile ivars 学习笔记
  2. Non Fragile ivars
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容