zoukankan      html  css  js  c++  java
  • OC 底层探索 07、类的结构分析2

    本文来探索类结构中 cache_t. 

    之前的文章OC底层探索04 中,已知如何找到类信息。本文我们对类信息中的 cache_t 进行探索。

    objc_class 结构 :

    从 OC底层探索04 中的指针和内存偏移,我们已知可通过指针平移获取相应位置信息,cache_t 的位置 = 8 + 8 =16

    一、cache_t 简析

    cache_t 的源码分析:

    CACHE_MASK_STORAGE:

    1、支持架构

    cache_t 源码有点长,我们可从截取的这部分代码中看到它对不同架构的支持:

    MacOS:i386

    模拟器:x86

    真机:arm64

    cache_t 中还可以发现一点,模拟器和真机的一些处理是不同的,业务开发中,我们所调试使用的最好的选择还是真机。 

    2、cache_t 内容

    cache_t --> 缓存 --> 增删改查

    模拟器:

    bucket_t

        explicit_atomic --> 我们点击进去可以看到 它是一些 C++ 代码,而其中重要的内容是 ‘T’ --> struct bucket_t * 。关于它,在这里我们暂时只需要知道它是原子性,为了我们缓存的安全性即可,更深层的后续再做探究。

        struct bucket_t *imp  sel

    _mask 

    真机 64:

    从下面代码,可观察到 maskAndBuckets 和一系列带有‘mask’ 的字段 --> 掩码、指针平移

    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16  // 真机64
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
        
        // How much the mask is shifted by.
        static constexpr uintptr_t maskShift = 48;
        
        // Additional bits after the mask which must be zero. msgSend
        // takes advantage of these additional bits to construct the value
        // `mask << 4` from `_maskAndBuckets` in a single instruction.
        static constexpr uintptr_t maskZeroBits = 4;
        
        // The largest mask value we can store.
        static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
        
        // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
        static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
        
        // Ensure we have enough bits for the buckets pointer.
        static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");

    _maskAndBuckets:--> bucket_t .

    _mask_unused:可能是苹果的预留,不管它 <-- "Don't know how to do ... ..." .

    另:

    _flags:标记

    _occupied:占位,内存占多少

    -->  cache_t 结构图:

     

    二、cache_t 缓存了什么 

    1、cache_t 缓存了方法

    运行工程(部分测试代码可能存在偏差,可自行编写),在未调用任何方法前,cache_t 内容:

     

    标线所示的值均为 0,继续执行,p 调用方法:

    对象 p 调用一次方法后,sel imp 不再为0,_mask 3 、occupied+1 --> 

    推测:方法执行一次后缓存在 cache_t 中。(mask occupied 文章后半部分探究)

    验证 _buckets 中存着调用过的方法:

    cache_t 源码中寻找是否有获取 _buckets 的方法 :

    继续 lldb 调试:

    上图,可验证 --> 方法首次执行后缓存在 cache_t 中:

    cache_t 中 sel 就是 对象p刚刚所调用方法的方法名

    imp 指向是MyPerson中的 方法的指针,指针地址0x0000000100001b50.

    2、cache_t 缓存集合 - buckets

    多个方法调用

    我们继续运行代码,让p调用方法2:

    由上可知:方法调用后都会存 buckets 中。

    同样通过OC底层探索04的指针和内存偏移,通过数组 index 属性操作:

    同样,我们取到了方法。

    思考:方法再调用会怎么样呢?

    方法只会缓存一份,方法调用的流程是什么样子的呢? --> 后续文章再对 objc_msgSend 流程进行探究。 

    2、cache_t 中 mask 和 occupied 是什么?

    运行工程,调用多个方法,进行 lldb 调试. 如下图:

    调试过程中,我们发现了几个问题:

    1、occupied 和 mask 是什么?它们的值为何是一直在变化的?

    2、cache_t 中方法的顺序和调用方法顺序为何不同

    3、buckets 中方法为何丢失不在了?

    寻找答案:

    1、去 cache_t 源码:

    进入 mask()occupied() 方法,发现没什么有用信息!

    但看到下面 incrementOccupied() - occupied 增量:

     

    源码中我们发现了 _occupied++mask() 的操作.

    全局搜索 ‘incrementOccupied( ’ --> cache_t 的insert 中做了 occupied/mask 的处理

    2、cache_t::insert  方法流程:

     1 ALWAYS_INLINE
     2 void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
     3 {
     4 #if CONFIG_USE_CACHE_LOCK
     5     cacheUpdateLock.assertLocked();
     6 #else
     7     runtimeLock.assertLocked();
     8 #endif
     9 
    10     ASSERT(sel != 0 && cls->isInitialized());
    11 
    12     // Use the cache as-is if it is less than 3/4 full
    13     mask_t newOccupied = occupied() + 1;// occupied():return _occupied; --> _occupied + 1
    14     unsigned oldCapacity = capacity(), capacity = oldCapacity;  // _mask.load() --> capacity 空间
    15     if (slowpath(isConstantEmptyCache())) {
    16         // 初始化,occupied=0,buckets()是空
    17         // Cache is read-only. Replace it.
    18         if (!capacity) capacity = INIT_CACHE_SIZE;// capacity 给1<<2的空间 4
    19         // 真正去向系统开辟内存
    20         reallocate(oldCapacity, capacity, /* freeOld */false);
    21     }
    22     else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) {
    23         // Cache is less than 3/4 full. Use it as-is.
    24         // cache < 3/4 capacity
    25     }
    26     else {
    27         capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;   // 扩容 如果capacity 不为空 扩容为当前的2倍;为空则去开辟 4
    28         if (capacity > MAX_CACHE_SIZE) {// 空间最大 1<<16 = 2^16
    29             capacity = MAX_CACHE_SIZE;
    30         }
    31         reallocate(oldCapacity, capacity, true);// 重新开辟空间,true:旧的free
    32     }
    33 
    34     bucket_t *b = buckets();
    35     mask_t m = capacity - 1;// 2^2-1=3  2^3-1=7
    36     mask_t begin = cache_hash(sel, m);// sel & mask
    37     mask_t i = begin;
    38 
    39     // Scan for the first unused slot and insert there.
    40     // There is guaranteed to be an empty slot because the
    41     // minimum size is 4 and we resized at 3/4 full.
    42     do {
    43         // 位置是空的可以放
    44         if (fastpath(b[i].sel() == 0)) {
    45             incrementOccupied();
    46             b[i].set<Atomic, Encoded>(sel, imp, cls);
    47             return;
    48         }
    49         // 此位置已经存值,且 .sel 就是传来的这个 sel 了
    50         if (b[i].sel() == sel) {
    51             // The entry was added to the cache by some other thread
    52             // before we grabbed the cacheUpdateLock.
    53             return;
    54         }
    55     } while (fastpath((i = cache_next(i, m)) != begin));// 再次哈希 --> (i+1)&mask != begin
    56 
    57     cache_t::bad_cache(receiver, (SEL)sel, cls);
    58 }

    cache_t::insert 逻辑流程概况图:

    从代码逻辑流程中,我们可以得到上面问题答案:

    1、occupied 从1->2->1->2->3 的原因:当 cache ≥ 3/4capacity 时,空间会重新开辟释放旧的空间,同时 occupied 手动置0.

    2、mask 值变化原因:  mask = capacity - 1,所以 它的值是 3 7 15......

    3、方法的缓存与调用顺序:缓存时通过哈希算法:sel & mask sel 存放位置 index 计算的,so 缓存是乱序的。

    4、buckets 中方法丢失:因 occupied>2 空间会被重新开辟,旧的空间会被释放free,之前的缓存的方法自然也会一起清掉。后面再掉的话会再次缓存。

    do{}while() 的流程:

    以上。

    问题:cache_t::insert 什么时候调用呢?--> 方法调用流程 --> objc_msgSend 消息发送流后程续文章继续探索。

  • 相关阅读:
    关于 Web 性能优化
    重命名某一路径下文件
    使用相对坐标定位元素
    使用appium做自动化时如何切换activity
    newman安装时遇到问题的解决
    pytesser的使用
    用户名密码的参数化(读取文件)
    百度登录
    Python 3中套接字编程中遇到TypeError: 'str' does not support the buffer interface的解决办法
    百度搜索设置下拉框的操作
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13704591.html
Copyright © 2011-2022 走看看