iOS 底层原理之cache_t分析

前言

oc 对象的本质与isaisa 类的底层原理结构 分别分析了isa和bits。类里面的成员变量还有superclass和cache,今天就来探究下cache的底层原理。

准备工作

objc4-818.2 源码

cache 结构分析

cache_t 结构图

cache结构图.jpg

首先探究cache的类型cache_t,源码中查看cache_t具体类型是结构体

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
   
    /*
     #if defined(__arm64__) && __LP64__
     #if TARGET_OS_OSX || TARGET_OS_SIMULATOR
     // __arm64__的模拟器
     #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
     #else
     //__arm64__的真机
     #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
     #endif
     #elif defined(__arm64__) && !__LP64__
     //32位 真机
     #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
     #else
     //macOS 模拟器
     #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
     #endif
     ******  中间是不同的架构之间的判断 主要是用来不同类型 mask 和 buckets 的掩码
    */
    
    public:
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    unsigned capacity() const;
    struct bucket_t *buckets() const;
    Class cls() const;
    void insert(SEL sel, IMP imp, id receiver);
    // 下面是基本上都是其他的方法的方法
};
复制代码
  • _bucketsAndMaybeMask变量uintptr_t占8字节和isa_t中的bits类似,也是一个指针类型里面存放地址
  • 联合体里有一个结构体和一个结构体指针_originalPreoptCache
  • 结构体中有三个成员变量_maybeMask_flags_occupied。__LP64__指的是Unix和Unix类系统(Linx和macOS)
  • _originalPreoptCache和结构体是互斥的,_originalPreoptCache初始时候的缓存,现在探究类中的缓存,这个变量基本不会用到
  • cache_t提供了公用的方法去获取值,以及根据不同的架构系统去获取mask和buckets

探究

  • 当需要缓存的方法所占的容量总容量3/4是就会直接走缓存流程
  • 探究一些底层源码就会发现,苹果的设计思想,做什么事情都会留有余地。一方面可能为了日后的优化或者扩展,另一方面可能是为了安全,内存对齐也是这样
  • 容量超过3/4,系统此时会进行两倍扩容,扩容的最大容量不会超过mask的最大值2^15
  • 扩容的时候会进行一步重要的操作,开辟新的内存,释放回收旧的内存,此时的freeOld = true

缓存方法

  • 首先拿到bucket()指向开辟这块内存首地址,也就是第一个bucket的地址,bucket()既不是数组也不是链表,只是一块连续的内存
  • hash函数根据缓存sel和mask,计算出hash下标。为什么需要mask呢?mask的实际作用是告诉系统你只能存前capacity – 1中的位置,比如capacity = 4时,缓存的方法只能存前面3个空位
  • 开始缓存,当前的位置没有数据,就缓存该方法。如果该位置有方法且和你的方法一样,说明该方法缓存过了,直接return。如果存在hash冲突,下标一样,sel不一样,此时会再次hash,冲突解决继续缓存

insert调用流程

  • 调用insert方法流程:[instance method]底层实现 objc_msgSend --> _objc_msgSend_uncached --> lookUpImpOrForward --> log_and_fill_cache --> cache_t::insert

总结

cache.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享