美文网首页
iOS objc_class之cache_t结构&流程解析

iOS objc_class之cache_t结构&流程解析

作者: Johnny_Z | 来源:发表于2020-09-18 00:26 被阅读0次

    静态源码分析


    objc_class 概况

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
     // ... 此处省略函数代码
    }
    
    • 从源码得知objc_class继承objc_object,其中有一个isa 联合体结构 8字节
    • superclass 是一个Class是一个指向objc_class的指针类型,8字节
      结论1: 根据结构体内存排布得知,cache的首地址在objec_object首地址偏移16字节的位置

    cache_t 源码

    struct cache_t {
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        explicit_atomic<struct bucket_t *> _buckets;
        explicit_atomic<mask_t> _mask;
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        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.");
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        // _maskAndBuckets stores the mask shift in the low 4 bits, and
        // the buckets pointer in the remainder of the value. The mask
        // shift is the value where (0xffff >> shift) produces the correct
        // mask. This is equal to 16 - log2(cache_size).
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
    
        static constexpr uintptr_t maskBits = 4;
        static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
        static constexpr uintptr_t bucketsMask = ~maskMask;
    #else
    #error Unknown cache mask storage type.
    #endif
        
    #if __LP64__
        uint16_t _flags;
    #endif
        uint16_t _occupied;
    
    public:
        static bucket_t *emptyBuckets();
        
        struct bucket_t *buckets();
        mask_t mask();
        mask_t occupied();
        void incrementOccupied();
        void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
        void initializeToEmpty();
    
        unsigned capacity();
        bool isConstantEmptyCache();
        bool canBeFreed();
    
    #if __LP64__
        bool getBit(uint16_t flags) const {
            return _flags & flags;
        }
        void setBit(uint16_t set) {
            __c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED);
        }
        void clearBit(uint16_t clear) {
            __c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED);
        }
    #endif
    
    #if FAST_CACHE_ALLOC_MASK
        bool hasFastInstanceSize(size_t extra) const
        {
            if (__builtin_constant_p(extra) && extra == 0) {
                return _flags & FAST_CACHE_ALLOC_MASK16;
            }
            return _flags & FAST_CACHE_ALLOC_MASK;
        }
    
        size_t fastInstanceSize(size_t extra) const
        {
            ASSERT(hasFastInstanceSize(extra));
    
            if (__builtin_constant_p(extra) && extra == 0) {
                return _flags & FAST_CACHE_ALLOC_MASK16;
            } else {
                size_t size = _flags & FAST_CACHE_ALLOC_MASK;
                // remove the FAST_CACHE_ALLOC_DELTA16 that was added
                // by setFastInstanceSize
                return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
            }
        }
    
        void setFastInstanceSize(size_t newSize)
        {
            // Set during realization or construction only. No locking needed.
            uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
            uint16_t sizeBits;
    
            // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
            // to yield the proper 16byte aligned allocation size with a single mask
            sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
            sizeBits &= FAST_CACHE_ALLOC_MASK;
            if (newSize <= sizeBits) {
                newBits |= sizeBits;
            }
            _flags = newBits;
        }
    #else
        bool hasFastInstanceSize(size_t extra) const {
            return false;
        }
        size_t fastInstanceSize(size_t extra) const {
            abort();
        }
        void setFastInstanceSize(size_t extra) {
            // nothing
        }
    #endif
    
        static size_t bytesForCapacity(uint32_t cap);
        static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    
        void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
        void insert(Class cls, SEL sel, IMP imp, id receiver);
    
        static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn, cold));
    };
    

    我们先把属性拎出来分别是

    • _buckets: 一个指向结构体bucket_t的指针
    • _mask:掩码,值为容量(capacity)-1,因为capacity为2^n,所以掩码的规律是高位为0低位为1的组合(00000111, 00001111, 00011111),可以用取&运算来做hash算法。
    • _flags:一些cache_t的标志写入其中,目前先不做考虑
    • _occupied:大小为已存入的方法数量。

    bucket_t 结构

    struct bucket_t {
    private:
        // IMP-first is better for arm64e ptrauth and no worse for arm64.
        // SEL-first is better for armv7* and i386 and x86_64.
    #if __arm64__
        explicit_atomic<uintptr_t> _imp;
        explicit_atomic<SEL> _sel;
    #else
        explicit_atomic<SEL> _sel;
        explicit_atomic<uintptr_t> _imp;
    #endif
    
        // Compute the ptrauth signing modifier from &_imp, newSel, and cls.
        uintptr_t modifierForSEL(SEL newSel, Class cls) const {
            return (uintptr_t)&_imp ^ (uintptr_t)newSel ^ (uintptr_t)cls;
        }
    
        // Sign newImp, with &_imp, newSel, and cls as modifiers.
        uintptr_t encodeImp(IMP newImp, SEL newSel, Class cls) const {
            if (!newImp) return 0;
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
            return (uintptr_t)
                ptrauth_auth_and_resign(newImp,
                                        ptrauth_key_function_pointer, 0,
                                        ptrauth_key_process_dependent_code,
                                        modifierForSEL(newSel, cls));
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
            return (uintptr_t)newImp ^ (uintptr_t)cls;
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
            return (uintptr_t)newImp;
    #else
    #error Unknown method cache IMP encoding.
    #endif
        }
    
    public:
        inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
    
        inline IMP imp(Class cls) const {
            uintptr_t imp = _imp.load(memory_order::memory_order_relaxed);
            if (!imp) return nil;
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
            SEL sel = _sel.load(memory_order::memory_order_relaxed);
            return (IMP)
                ptrauth_auth_and_resign((const void *)imp,
                                        ptrauth_key_process_dependent_code,
                                        modifierForSEL(sel, cls),
                                        ptrauth_key_function_pointer, 0);
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
            return (IMP)(imp ^ (uintptr_t)cls);
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
            return (IMP)imp;
    #else
    #error Unknown method cache IMP encoding.
    #endif
        }
    
        template <Atomicity, IMPEncoding>
        void set(SEL newSel, IMP newImp, Class cls);
    };
    

    把重要的东西拎出来:

    • 两个属性: _imp_sel;
    • 两个方法: sel()imp();

    执行代码辅助分析


    准备如下代码

    @interface LGPerson : NSObject
    @property (nonatomic, copy) NSString *lgName;
    @property (nonatomic, strong) NSString *nickName;
    - (void)sayHello;
    - (void)sayCode;
    - (void)sayMaster;
    - (void)sayNB;
    + (void)sayHappy;
    @end
    
    @implementation LGPerson
    - (void)sayHello{
        NSLog(@"LGPerson say : %s",__func__);
    }
    
    - (void)sayCode{
        NSLog(@"LGPerson say : %s",__func__);
    }
    
    - (void)sayMaster{
        NSLog(@"LGPerson say : %s",__func__);
    }
    
    - (void)sayNB{
        NSLog(@"LGPerson say : %s",__func__);
    }
    
    + (void)sayHappy{
        NSLog(@"LGPerson say : %s",__func__);
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            LGPerson *p  = [LGPerson alloc];
            Class pClass = [LGPerson class];
    
            [p sayHello];
            [p sayCode];
            [p sayMaster];
            [p sayNB];
            NSLog(@"%@",pClass);
        }
        return 0;
    }
    

    我们在[p sayHello];加入断点运行,分析如下图

    image.png

    但我们运行到[p sayCode];打印时

    image.png
    我们不妨通过cache_t 里面的方法buckets (),以及进行下列分析
    image.png

    为什么我们不直接拿cache_t里面的_bucket来操作呢,因为这个指针类型其实被explicit_atomic修饰了,无法直接打印。接下来的调用sel()方法亦是如此.

    结论2:我们在cache内的bucket找到了我们的调用方法

    深入分析cache_t缓存流程

    cache_t方法实现的文件中找到

    image.png

    具体流程为:
    1、 计算当前缓存占用的数量
    mask_t newOccupied = occupied() + 1;
    2、执行条件:

    • 如果当前未空缓存,也就是第一次进来: 申请开辟内存 容量为4
    ALWAYS_INLINE
    void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
    {
       bucket_t *oldBuckets = buckets();
       bucket_t *newBuckets = allocateBuckets(newCapacity);
    
       // Cache's old contents are not propagated. 
       // This is thought to save cache memory at the cost of extra cache fills.
       // fixme re-measure this
    
       ASSERT(newCapacity > 0);
       ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
    
       setBucketsAndMask(newBuckets, newCapacity - 1);
       
       if (freeOld) {
           cache_collect_free(oldBuckets, oldCapacity);
       }
    }
    
    • 如果存入缓存后小于等于3/4,不做处理
    • 如果前两个条件都不满足,则开始扩容;扩容规则为:放弃就缓存,新缓存大小为原来的2倍,并且重新梳理缓存

    3、针对这一次的方法进行bucket内部存储

    相关文章

      网友评论

          本文标题:iOS objc_class之cache_t结构&流程解析

          本文链接:https://www.haomeiwen.com/subject/rgvryktx.html