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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335