• ios-散列表


    ios 引用计数 retain release过程中不免要操作一张散列表(taggedPoint 不在此次讨论范围内)

    那么散列表究竟是个什么样的结构,它与引用计数的关系是什么样的,这是我们此次通过源码探究的主题

    进入libobjc 源码 (具体源码参考 - objc4-841.13可调试/编译源码更新(for M1))

    以retain为例 涉及到一个结构

    在这里插入图片描述

    在这里插入图片描述

    引出StripedMap

    在这里插入图片描述

    StripedMap 中包含一个数组,数组个数 – arm 8; x86-64 64

    以arm为例,就是 StripedMap中存储了8张表

    在这里插入图片描述

    我们具体操作的时候 是取StripedMap 中的某一张表

    取表的过程

    • 通过一个key,也就是object的指针,也就是地址,可以认为是唯一的(具体虚拟地址偏移不在此次讨论范围),用过字典,应该知道这是什么意思

    • 把指针转换为 整型地址,也就是一个无符号整型变量 — uint_var

    • 哈希函数 - (uint_var >> 4) ^ (unint_var >> 9) % 8(不做说明,后面的StripeCount 都按8计,也就是arm结构), 得到的是 数组中的索引index, 范围0 ~ 7

    • 通过 array[index] 取出 SideTable

    由于 StripedMap 是全局的,必然存在访问问题,表中的元素 加锁 解锁操作

    在这里插入图片描述

    上面最开始的retain示例中,SideTables(), 就是获取到了 StripedMap

    SideTables()[this] 就是 StripedMap[对象指针], 上面已提到过运算符[]重载

    SideTable

    在这里插入图片描述

    你会发现操作这两表时 为了线程安全,避免不了锁的操作,必然存在性能消耗与效率问题

    既然 表是全局的,必然存在回收机制

    以refcnts为例, 拿到 SideTable之后,进一步 取RefcountMap

    StripedMap[对象指针].refcnts —> RefcountMap

    在这里插入图片描述
    其实 RefcountMap 为了安全考虑,掩盖了指针

    继续向上溯源 objc::DenseMap

    在这里插入图片描述

    继续 DenseMapBase

    在这里插入图片描述

    在这里插入图片描述

    又又出现了 运算符[]重载

    • StripedMap[对象指针].refcnts —> RefcountMap

    • StripedMap[对象指针].refcnts[对象指针] —> 引用计数存储的引用(简单理解就是获取到了 对象的引用计数变量)

      • 其实 并不是直接取出引用计数了
      • 还涉及一层,只是把这里的细节全展示,你会一个感觉,脑仁疼
      • 由于这块c++做了封装,所以不拿出来分析

    理解散列表

    对于散列表的理解,需要自带一些抽象气质

    • 全局StripdMap可以理解为三层套娃结构

    • 第一层,通过 对象指针地址 经过哈希函数运算,得到 StripdMap中数组结构的索引

    • 第二层,通过hash得到的索引,从 StripdMap中数组结构 中取出 Sidetable

    • 可以选择是使用SideTable 的引用计数表 还是 弱引用表

    • 引用计数表(SideTable成员refcnts)为例,refcnts[对象指针] --> Bucket 结构

      在这里插入图片描述

      • BucketT 是个 键值结构

      在这里插入图片描述

    • Bucket中 取出value

    • 还不算完,取出的value不是纯粹意义上整型变量,而应该看待为一个 bit结构,只有相应位置才存储引用计数,其余位有特殊功能

    • Bucket - value 是从低位 第2个bit位开始存储 引用计数的,也就是每次加减 都是 2 或者 是 1<<1 这样的操作

      在这里插入图片描述

    引用计数表 - 引用计数逻辑

    引用计数的操作主要包含两部分 retain / release

    • retain

      • taggedPoint 没有引用计数操作, 非taggedPoint 也就是nonpoint_isa 与 纯isa存在引用计数操作

      • 未开启指针优化的话,就是纯isa,只操作散列表中的引用计数

      • nonpoint_isa 操作isa 中的 extra_rc 与 散列表,散列表通过 isa中的位 has_sidetable_rc 来标识

      • 以下主要说明的是 nonpoint_isa的操作过程

      • isa中取出 extra_rc, 如果+1 发现 19位 存满了,就extra_rc 折半,留一半,另一半存储到 散列表中,同时 has_sidetable_rc 标识为1

      • 散列表的操作

      • StripedMap 对象指针 hash运算,从 StripedMap.array中取出 SideTable

      • SideTable.refcnts(引用计数表) 再次通过 对象指针 另一个hash函数运算,取出Bucket结构(结构)

        • Buckets(一个一个的Bucket)结构, 如果hash得出的索引位置 正好命中了key,就没必要再继续遍历查找了,如果hash没有命中,就只能按部就班挨个偏移位置查找key是否相等 当然取决于hash函数的设计了 冲突概率越少 hash函数越好
      • 散列表 Bucket.value 低位开始 第二个位置 + 1,也就是 + 2,存储

      • 当然散列表由于是全局,所以操作需要 加锁 开锁

    • release 与retain相反的过程

      • 此处讨论的还是 nonpoint_isa 的全流程

      • 首先 isa :: extra_rc 减 1,如果此时计数为0,同时 has_sidetable_rc 为0,没有引用散列表存储计数,就发送 dealloc

      • 如果 extra_rc 减1之后 不为0 就没有其他操作

      • 如果 extra_rc 减1为0, 同时 has_sidetable_rc 为1,就 从散列表 借出 extra_rc 所能存储的总数的一半,剩下的部分继续存储在散列表里, 取出的部分存到 extra_rc 里, 具体散列表的操作跟 retain过程中的一样,此处就不重复了

      • 如果 extra_rc 从散列表 借完计数之后,散列表计数为0,就删除散列表中的bucket结点, has_sidetable_rc 置为0

      • 下次release 继续 从 extra_rc 减1,直到减为0, 同时 has_sidetable_rc为0

      • 发送 dealloc消息 (这就是为什么dealloc 不能调用父类dealloc的缘故了)

  • 相关阅读:
    JDK8的特性
    128.《usestate与usestate区别及应用场景》
    npm排错记录
    Django 07
    c# 与modbus
    SpringMVC 学习(十)之异常处理
    Android笔记(四)Activity之间传递可序列化的数据的优化处理
    图像语义分割概述
    MES的发展历程及功能模块
    PHP 循环控制 学习资料
  • 原文地址:https://blog.csdn.net/qq_42431419/article/details/126290583