preface

Struct objc_class*; struct objc_class*; struct objc_class*;

Struct objc_class struct objc_class struct objc_class struct objc_class

1. Objc1.0 objc_class

Objc-runtime-old. h objc_class (deprecated)

3. Definition of objc_class in objc-Runtime-new. h

From objc runtime – new. H file objc_class as you can see, there are four member variables, respectively from objc_object inherited the isa, superClass, cache, bits. Isa and superClass have been analyzed above. This paper will not elaborate on them. In this paper, cache and bits are analyzed first

Translation memory

Int main(int argc, const char * argv[]) {@autoreleasepool {int a[5] = {1,1,3,4,5}; int *b = a; NSLog(@"%p - %p - %p",&a , &a[0] , &a[1]); NSLog(@"%p - %p - %p",b,b+1,b+2); for (int i = 0; i < 5; i++) { int value = *(b + i); NSLog(@"%d",value); } } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-05 14:09:29. 561329 + 0800 translation memory (1661-24500) 0x7ff7Bfeff2B0-0x7ff7BFeff2b0-0x7ff7BFeff2b4 2022-02-05 14:09:29.561653+0800 Memory translation [1661:24500] 0x7ff7Bfeff2B0 - 0x7ff7BFeff2b4-0x7ff7BFeff2b8 2022-02-05 14:09:29.561689+0800 Memory translation [1661:24500] 1 2022-02-05 14:09:29.561737+0800 Memory translation [1661:24500] 1 2022-02-05 14:09:29.561777+0800 Memory translation [1661:24500] 3 2022-02-05 14:09:29.561801+0800 Memory translation [1661:24500] 4 2022-02-05 14:09:29.561823+0800 Memory translation [1661:24500] 5Copy the code

As we can see from the above print, successive memory addresses can be translated to obtain subsequent values. Therefore, given the memory address of a class, we can obtain the bits by translation.

To get bits, you need to translate it from the first address. Given that both ISA and superClass are Pointers to 8 bytes each, you only need to get the size of the cache to get bits.

Check the structure of the cache, which is a cache_t:

typedef unsigned long uintptr_t; #if __LP64__ typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits #else typedef uint16_t mask_t; #endif struct cache_t { private: explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8 bytes union {struct {explicit_atomic<mask_t> _maybeMask; //4 bytes #if __LP64__ uint16_t _flags; // 3 bytes #endif uint16_t _occupied; / / 2 bytes}; explicit_atomic<preopt_cache_t *> _originalPreoptCache; / / 8 bytes}; // Cache_t takes up 16 bytes; // omit static variables and methods (do not take up structure memory)}Copy the code

From the above analysis, we can calculate that cache_t occupies 16 bytes, so we can translate the first address of the class by 32 bytes to get the information in bits

bitsAnalysis of the

Get class attributes:

The process for obtaining the property of the class is as follows: xqperson. class -> class_data_bits_t -> class_rw_T -> property_array_t -> property_list_t -> property_t.

@interface XQPerson : NSObject @property(nonatomic,copy)NSString* name; @property(nonatomic,assign)char sex; @property(nonatomic,assign)int height; @ end @ implementation XQPerson @ the end * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LLDB debugging process * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / get XQPerson memory address  (lldb) x/4g XQPerson.class 0x100008318: 0x00000001000082f0 0x000000010036e140 0x100008328: 0x0000000100366390 0x0000802400000000 // The first address of the class is isa translation 0x20 bytes (LLDB) p/x 0x100008318 + 0x20 (long) $1 = 0x0000000100008338 // Type is converted to class_datA_bits_t * structure pointer (LLDB) p (class_datA_bits_t *)$1 (class_data_bits_t*)$2 = 0x0000000100008338 (LLDB) p $2->data() (class_rw_t *) $3 = 0x0000000101C0d500 // Get property_array_t (LLDB) p $3->properties() (const property_array_t) $4 = { list_array_tt<property_t, property_list_t, RawPtr> = {= {list = {PTR = 0x00000001000081F8} arrayAndFlag = 4295000568}}} // Fetch list (LLDB) p $4.list (const RawPtr<property_list_t>) $5 = {PTR = 0x00000001000081F8} PTR (LLDB) p $5. PTR (property_list_t *const) $6 = 0x00000001000081F8 // Retrieve the pointer to the value, $7 = {entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 3)} p $7.get(0) (property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_name") (lldb) p $7.get(1) (property_t) $9 = (name = "sex", attributes = "Tc,N,V_sex") (lldb) p $7.get(2) (property_t) $10 = (name = "height", attributes = "Ti,N,V_height")Copy the code

Gets a class member variable (ivars)

The process of getting a member variable is slightly different from getting an attribute; member variables are stored only in class_ro_T. Member variables (IVars) : xqperson. class -> class_datA_bits_t -> class_rw_T -> class_ro_t -> ivar_list_t -> ivar_t

struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif union { const uint8_t * ivarLayout; Class nonMetaclass; }; explicit_atomic<const char *> name; // With ptrauth, this is signed if it points to a small list, but // may be unsigned if it points to a big list. void *baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; / / omit part of the code} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LLDB debugging process * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * (LLDB) x / 4 g XQPerson. Class 0 x100008368: 0x0000000100008340 0x000000010036e140 0x100008378: 0x0000000100366390 0x0000802400000000 (lldb) p (class_data_bits_t*)(0x100008368 + 0x20) (class_data_bits_t *) $1 = 0x0000000100008388 (lldb) p $1->data() (class_rw_t *) $2 = 0x00000001011c5cc0 (lldb) p $2->ro() (const class_ro_t *) $3 = 0x0000000100008128 (lldb) p *$3 (const class_ro_t) $4 = { flags = 388 instanceStart = 8 instanceSize = 24 reserved = 0  = { ivarLayout = 0x0000000100003d0b "\U00000011" nonMetaclass = 0x0000000100003d0b } name = { std::__1::atomic<const char *> = "XQPerson" { Value = 0x0000000100003d02 "XQPerson" } } baseMethodList = 0x0000000100008170 baseProtocols = nil  ivars = 0x0000000100008220 weakIvarLayout = 0x0000000000000000 baseProperties = 0x0000000100008288 _swiftMetadataInitializer_NEVER_USE = {} } (lldb) p $4.ivars (const ivar_list_t *const) $5 = 0x0000000100008220 (lldb) p  *$5 (const ivar_list_t) $6 = { entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3) } (lldb) p $6.get(0) (ivar_t) $7 = { offset = 0x00000001000082d8 name = 0x0000000100003f0f "_sex" type = 0x0000000100003f5c "c" alignment_raw = 0 size = 1 } (lldb) p $6.get(1) (ivar_t) $8 = { offset = 0x00000001000082e0 name = 0x0000000100003f14 "_height" type = 0x0000000100003f5e "i" alignment_raw = 2 size = 4 } (lldb) p $6.get(2) (ivar_t) $9  = { offset = 0x00000001000082e8 name = 0x0000000100003f1c "_name" type = 0x0000000100003f60 "@\"NSString\"" alignment_raw = 3 size = 8 }Copy the code

Gets the instance method of the class

Method_t, but there are no member variables in the method_t structure, so the direct output is empty. You need to get the big structure in method_t.

Xqperson. class -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t -> method_t -> Big (if small is not used).

struct method_t { static const uint32_t smallMethodListFlag = 0x80000000; method_t(const method_t &other) = delete; // The representation of a "big" method. This is the traditional // representation of three pointers storing the selector, types // and implementation. struct big { SEL name; const char *types; MethodListIMP imp; }; private: bool isSmall() const { return ((uintptr_t)this & 1) == 1; } // The representation of a "small" method. This stores three // relative offsets to the name, types, and implementation. struct small { // The name field either refers to a selector (in the shared // cache) or a selref (everywhere else). RelativePointer<const void *> name; RelativePointer<const char *> types; RelativePointer<IMP> imp; bool inSharedCache() const { return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)this)); }}; small &small() const { ASSERT(isSmall()); return *(struct small *)((uintptr_t)this & ~(uintptr_t)1); } IMP remappedImp(bool needsLock) const; void remapImp(IMP imp); objc_method_description *getSmallDescription() const; public: static const auto bigSize = sizeof(struct big); static const auto smallSize = sizeof(struct small); // The pointer modifier used with method lists. When the method // list contains small methods, set the bottom bit of the pointer. // We use that bottom bit elsewhere to distinguish between big // and small methods. struct pointer_modifier { template <typename ListType> static method_t *modify(const ListType &list, method_t *ptr) { if (list.flags() & smallMethodListFlag) return (method_t *)((uintptr_t)ptr | 1); return ptr; }}; big &big() const { ASSERT(! isSmall()); return *(struct big *)this; } / / omit part of the code} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LLDB debugging process * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * (LLDB) x / 4 g XQPerson. Class 0x100008368: 0x0000000100008340 0x000000010036e140 0x100008378: 0x0000000100366390 0x0000802400000000 (lldb) p (class_data_bits_t*)(0x100008368 + 0x20) (class_data_bits_t *) $1 = 0x0000000100008388 (lldb) p $1->data() (class_rw_t *) $2 = 0x0000000101804160 (lldb) p $2->methods() (const method_array_t) $3 = { list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = { = { list = { ptr = 0x0000000100008170 } arrayAndFlag = 4295000432 } } } (lldb) p $3.list (const method_list_t_authed_ptr<method_list_t>) $4 = { ptr = 0x0000000100008170 } (lldb) p $4.ptr (method_list_t *const)  $5 = 0x0000000100008170 (lldb) p *$5 (method_list_t) $6 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 7) } (lldb) p $6.get(0).big() (method_t::big) $7 = { name = "name" types = 0x0000000100003f6c "@16@0:8" imp = 0x0000000100003b40 (KCObjcBuild`-[XQPerson name]) } (lldb) p $6.get(1).big() (method_t::big) $8 = { name = ".cxx_destruct" types = 0x0000000100003fa5 "v16@0:8" imp = 0x0000000100003c20 (KCObjcBuild`-[XQPerson .cxx_destruct]) } (lldb) p $6.get(2).big() (method_t::big) $9 = { name = "setName:" types = 0x0000000100003f74 "v24@0:8@16" imp = 0x0000000100003b70 (KCObjcBuild`-[XQPerson setName:]) } (lldb) p $6.get(3).big() (method_t::big) $10 = { name = "setHeight:" types = 0x0000000100003f9a "v20@0:8i16" imp = 0x0000000100003c00 (KCObjcBuild`-[XQPerson setHeight:]) } (lldb) p $6.get(4).big() (method_t::big) $11 = { name = "height" types = 0x0000000100003f92 "i16@0:8" imp = 0x0000000100003be0 (KCObjcBuild`-[XQPerson height]) } (lldb) p $6.get(5).big() (method_t::big) $12 = { name = "sex" types = 0x0000000100003f7f "c16@0:8" imp = 0x0000000100003ba0 (KCObjcBuild`-[XQPerson sex]) } (lldb) p $6.get(6).big() (method_t::big) $13 = { name = "setSex:" types = 0x0000000100003f87 "v20@0:8c16" imp = 0x0000000100003bc0 (KCObjcBuild`-[XQPerson setSex:]) }Copy the code

Obtaining a class method is similar to obtaining an instance method path. You only need to obtain the metaclass first and then perform the above operations, which are not listed here

Gets the protocol of the class

The protocol for retrieving a class first needs to follow a protocol

Declaration of agreement and compliance with agreement


@protocol XQProtocol <NSObject>

-(void)eatFood;

+(void)shopping;

@end

@interface XQPerson : NSObject<XQProtocol>
@property(nonatomic,copy)NSString* name;
@property(nonatomic,assign)char sex;
@property(nonatomic,assign)int height;
@end

@implementation XQPerson
-(void)eatFood{
    
}

+(void)shopping{
    
}
@end

Copy the code

Protocol obtaining process: XJPerson.class -> class_data_bits_t -> class_rw_t -> protocol_array_t -> protocol_list_t -> protocol_ref_t -> protocol_t -> list_t -> big

Protocol_list_t, PROTOCOL_ref_t, protocol_t

//protocol_ref_t is protocol_t * typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped // Values for protocol_t->flags #define PROTOCOL_FIXED_UP_2 (1<<31) // must never be set by compiler #define PROTOCOL_FIXED_UP_1 (1<<30) // must never be set by compiler #define PROTOCOL_IS_CANONICAL (1<<29) // must never  be set by compiler // Bits 0.. 15 are reserved for Swift's use. #define PROTOCOL_FIXED_UP_MASK (PROTOCOL_FIXED_UP_1 | PROTOCOL_FIXED_UP_2) struct protocol_t : objc_object { const char *mangledName; struct protocol_list_t *protocols; method_list_t *instanceMethods; method_list_t *classMethods; method_list_t *optionalInstanceMethods; method_list_t *optionalClassMethods; property_list_t *instanceProperties; uint32_t size; // sizeof(protocol_t) uint32_t flags; // Fields below this point are not always present on disk. const char **_extendedMethodTypes; const char *_demangledName; property_list_t *_classProperties; const char *demangledName(); const char *nameForLogging() { return demangledName(); } bool isFixedUp() const; void setFixedUp(); bool isCanonical() const; void clearIsCanonical(); # define HAS_FIELD(f) ((uintptr_t)(&f) < ((uintptr_t)this + size)) bool hasExtendedMethodTypesField() const { return HAS_FIELD(_extendedMethodTypes); } bool hasDemangledNameField() const { return HAS_FIELD(_demangledName); } bool hasClassPropertiesField() const { return HAS_FIELD(_classProperties); } # undef HAS_FIELD const char **extendedMethodTypes() const { return hasExtendedMethodTypesField() ? _extendedMethodTypes : nil; } property_list_t *classProperties() const { return hasClassPropertiesField() ? _classProperties : nil; }}; struct protocol_list_t { // count is pointer-sized by accident. uintptr_t count; //protocol_list_t gets protocol_ref_t through list[0], and then converts to (protocol_t*) body pointer PROTOCOL_ref_t list[0]; // variable-size size_t byteSize() const { return sizeof(*this) + count*sizeof(list[0]); } protocol_list_t *duplicate() const { return (protocol_list_t *)memdup(this, this->byteSize()); } typedef protocol_ref_t* iterator; typedef const protocol_ref_t* const_iterator; const_iterator begin() const { return list; } iterator begin() { return list; } const_iterator end() const { return list + count; } iterator end() { return list + count; }};Copy the code

LLDB debugging process

(lldb) x/4g XQPerson.class 0x100008738: 0x0000000100008710 0x000000010036e140 0x100008748: 0x0000000100366390 0x0000802400000000 (lldb) p (class_data_bits_t*)(0x100008738 + 0x20) (class_data_bits_t *) $1 = 0x0000000100008758 (lldb) p $1->data() (class_rw_t *) $2 = 0x0000000101ac9a90 (lldb) p $2->protocols() (const protocol_array_t) $3 = { list_array_tt<unsigned long, protocol_list_t, RawPtr> = { = { list = { ptr = 0x0000000100008488 } arrayAndFlag = 4295001224 } } } (lldb) p $3.list (const RawPtr<protocol_list_t>) $4 = { ptr = 0x0000000100008488 } (lldb) p $4.ptr (protocol_list_t *const) $5 = 0x0000000100008488 (lldb) p *$5 (protocol_list_t) $6 = (count = 1, List = protocol_ref_t [] @0x00007FDD2DE93768) // Get protocol_ref_t (LLDB) p $6.list[0] (protocol_ref_t) $7 = 4295002056 (LLDB) p (protocol_t *)$7 // Type conversion to PROTOCOL_T * (protocol_t *)$8 = 0x00000001000087c8 (LLDB) p *$8 (protocol_t) $9 = { objc_object = { isa = { bits = 4298563784 cls = 0x000000010036e0c8 = { nonpointer = 0 has_assoc = 0 has_cxx_dtor = 0 shiftcls = 537320473 magic = 0 weakly_referenced = 0 unused = 0 has_sidetable_rc = 0 extra_rc = 0 } } } mangledName = 0x0000000100003b1b "XQProtocol" protocols = 0x00000001000083b8 instanceMethods = 0x00000001000083d0 classMethods = 0x00000001000083f0 optionalInstanceMethods = nil optionalClassMethods = nil instanceProperties = nil size = 96 flags = 0 _extendedMethodTypes = 0x0000000100008410 _demangledName = 0x0000000000000000 _classProperties = nil} // Get the instance method in the protocol (lldb) p $9.instanceMethods (method_list_t *) $10 = 0x00000001000083d0 (lldb) p *$10 (method_list_t) $11 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1) } (lldb) p $11.get(0).big() (method_t::big) $12 = { name = "eatFood" types = 0x0000000100003daf "v16@0:8" imp $13 = 0x00000001000083f0 (LLDB) p * $13 (method_list_t) $14 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1) } (lldb) p $14.get(0).big() (method_t::big) $15 = { name = "shopping" types = 0x0000000100003daf "v16@0:8" imp = 0x0000000000000000 }Copy the code

cache_tAnalysis of the

Cache_t partial source parsing


{
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;            // 8 根据不同架构决定存放不同的信息,X86_64存放buckets,arm64高16位存储mask,低48位buckets。
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;              // 4 当前缓存区的容量,arm64架构下不使用。
#if __LP64__
            uint16_t                   _flags;                  // 2
#endif
            uint16_t                   _occupied;               // 2 当前缓存的方法个数。
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
    };

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
    
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
    static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16  // 真机环境会使用这些变量
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

    // 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.");

#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
#if __has_feature(ptrauth_calls)
    // 63..60: hash_mask_shift
    // 59..55: hash_shift
    // 54.. 1: buckets ptr + auth
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        uintptr_t value = (uintptr_t)cache->shift << 55;
        // masks have 11 bits but can be 0, so we compute
        // the right shift for 0x7fff rather than 0xffff
        return value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60);
    }
#else
    // 63..53: hash_mask
    // 52..48: hash_shift
    // 47.. 1: buckets ptr
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        return (uintptr_t)cache->hash_params << 48;
    }
#endif
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
    // _maybeMask is unused, the mask length is stored in the low 4 bits

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif
    //缓存为空缓存,第一次判断使用
    bool isConstantEmptyCache() const;
    bool canBeFreed() const;
    //可使用总容量,为capacity - 1
    mask_t mask() const;

#if CONFIG_USE_PREOPT_CACHES
    void initializeToPreoptCacheInDisguise(const preopt_cache_t *cache);
    const preopt_cache_t *disguised_preopt_cache() const;
#endif
    //方法缓存个数加一
    void incrementOccupied();
    // 设置buckets和mask
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    // 重新开辟内存
    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    // 根据oldCapacity回收oldBuckets
    void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);

    static bucket_t *emptyBuckets();
    static bucket_t *allocateBuckets(mask_t newCapacity);
    static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));

public:
    // The following four fields are public for objcdt's use only.
    // objcdt reaches into fields while the process is suspended
    // hence doesn't care for locks and pesky little details like this
    // and can safely use these.
    // 开辟的总容量
    unsigned capacity() const;
    // 获取buckets
    struct bucket_t *buckets() const;
    // 获取class
    Class cls() const;

#if CONFIG_USE_PREOPT_CACHES
    const preopt_cache_t *preopt_cache() const;
#endif
    // 获取已缓存的数量
    mask_t occupied() const;
    void initializeToEmpty();

#if CONFIG_USE_PREOPT_CACHES
    bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const;
    bool shouldFlush(SEL sel, IMP imp) const;
    bool isConstantOptimizedCacheWithInlinedSels() const;
    Class preoptFallbackClass() const;
    void maybeConvertToPreoptimized();
    void initializeToEmptyOrPreoptimizedInDisguise();
#else
    inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
    inline bool shouldFlush(SEL sel, IMP imp) const {
        return cache_getImp(cls(), sel) == imp;
    }
    inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; }
    inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
#endif
    // 将调用的方法插入到buckets所在的内存区域
    /*
     获取当前已缓存方法个数(第一次为0),然后+1。
     从获取缓存区容量,第一次为0。
     判断是否为第一次缓存方法,第一次缓存就开辟capacity(1 << INIT_CACHE_SIZE_LOG2(X86_64为2,arm64为1)) * sizeof(bucket_t)大小的内存空间,将bucket_t *首地址存入_bucketsAndMaybeMask,将newCapacity - 1的mask存入_maybeMask,_occupied设置为0。
     不是第一次缓存,就判断是否需要扩容(已缓存容量超过总容量的3/4或者7/8),需要扩容就双倍扩容(但不能大于最大值),然后像第三步一样重新开辟内存,并且回收旧缓存区的内存。
     哈希算法算出方法缓存的位置,do{} while()循环判断当前位置是否可存,如果哈希冲突了,就一直再哈希,直到找到可存入的位置位置,如果找完都未找到就调用bad_cache函数。
     */
    void insert(SEL sel, IMP imp, id receiver);
    void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
    void destroy();
    void eraseNolock(const char *func);

    static void init();
    static void collectNolock(bool collectALot);
    static size_t bytesForCapacity(uint32_t cap);

#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
}

Copy the code

cache_tPartial member variable resolution

_bucketsAndMaybeMask Stores different information according to architecture. X86_64 stores buckets, arm64 stores masks with 16 bits higher and 48 bits lower. _maybeMask Current cache capacity, not used in arm64 architecture. _occupied Number of cached methods.

_bucketsAndMaybeMask authentication in X86_64:


(lldb) x/4g XQPerson.class
0x100008738: 0x0000000100008710 0x000000010036e140
0x100008748: 0x0000000100366390 0x0000802400000000
(lldb) p/x (cache_t*)(0x100008738 + 0x10)
(cache_t *) $1 = 0x0000000100008748
(lldb) p *$1
(cache_t) $2 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 4298531728
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 0
        }
      }
      _flags = 32804
      _occupied = 0
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0000802400000000
      }
    }
  }
}
(lldb) p $1->buckets()
(bucket_t *) $3 = 0x0000000100366390
(lldb) p/x 4298531728
(long) $4 = 0x0000000100366390


Copy the code

According to LLDB debugging printing, 3=3=3=4, so the pointer of _bucketsAndMaybeMask points to buckets

Bucket_t struct member parsing:

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 // omit function}Copy the code

As you can see from the bucket_t structure members, cache_t stores methods. The following diagram can be obtained:

Cache_t key function parsing:

Insert function parsing:

#if CACHE_END_MARKER || (__arm64__ && ! __LP64__) INIT_CACHE_SIZE_LOG2 = 2, #else INIT_CACHE_SIZE_LOG2 = 1, #endif void cache_t::insert(SEL sel, IMP imp, id receiver) { runtimeLock.assertLocked(); // Never cache before +initialize is done if (slowpath(! cls()->isInitialized())) { return; } if (isConstantOptimizedCache()) { _objc_fatal("cache_t::insert() called with a preoptimized cache for %s", cls()->nameForLogging()); } #if DEBUG_TASK_THREADS return _collecting_in_critical(); #else #if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); #endif ASSERT(sel ! = 0 && cls()->isInitialized()); Use the cache as-is if until we exceed our expected fill ratio. Mask_t newOccupied = occupied(); unsigned oldCapacity = capacity(), capacity = oldCapacity; if (slowpath(isConstantEmptyCache())) { // Cache is read-only. Replace it. if (! capacity) capacity = INIT_CACHE_SIZE; // create a memory sizeof capacity * sizeof(bucket_t). // store the first address of 'bucket_t' into '_bucketsAndMaybeMask', // Store the 'mask' of 'newCapacity - 1' into '_maybeMask', // _occupied sets 0 reallocate(oldCapacity, capacity, /* freeOld */false); } // Not exceeding 3/4 or 7/8 of capacity (depending on architecture), Else if (fastPATH (newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity)) {// Cache is less than 3/4 or 7/8  full. Use it as-is. } #if CACHE_ALLOW_FULL_UTILIZATION else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) { // Allow 100% cache utilization for small buckets. Use it as-is. } #endif else { // If the capacity exceeds 3/4 or 7/8, double the capacity. capacity * 2 : INIT_CACHE_SIZE; if (capacity > MAX_CACHE_SIZE) { capacity = MAX_CACHE_SIZE; } // create a memory sizeof capacity * sizeof(bucket_t), // store the first address of 'bucket_t' into '_bucketsAndMaybeMask', // Store 'mask' of 'newcapacity-1' to '_maybeMask', // _occupied: 0; discard old memory reallocate(oldCapacity, capacity, true); } bucket *b = buckets(); mask_t m = capacity - 1; Mask_t begin = cache_hash(sel, m); mask_t i = begin; // Scan for the first unused slot and insert there. // There is guaranteed to be an empty slot. do { if (fastPath (b[I].sel() == 0)) {// _occupied+ 1; b[i].set<Atomic, Encoded>(b, sel, imp, cls()); return; } if (b[I].sel() == sel) { return // The entry was added to the cache by some other thread // before we grabbed the cacheUpdateLock. return; } } while (fastpath((i = cache_next(i, m)) ! = begin)); bad_cache(receiver, (SEL)sel); #endif // ! DEBUG_TASK_THREADS }Copy the code

Capacity function parsing, based on different architectures are as follows:

Unsigned cache_t::capacity() const {return mask()? mask()+1 : 0; } #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED mask_t cache_t::mask() const { return _maybeMask.load(memory_order_relaxed); } #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 || CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS mask_t cache_t::mask() const { uintptr_t maskAndBuckets = _bucketsAndMaybeMask.load(memory_order_relaxed); return maskAndBuckets >> maskShift; // maskShift = 48 } #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 mask_t cache_t::mask() const { uintptr_t maskAndBuckets = _bucketsAndMaybeMask.load(memory_order_relaxed); uintptr_t maskShift = (maskAndBuckets & maskMask); return 0xffff >> maskShift; }Copy the code

Capacity Calculates the total number of caches

unsigned cache_t::capacity() const
{
    return mask() ? mask()+1 : 0; 
}
Copy the code

Cache_fill_ratio: calculates the maximum capacity occupied by the cache. The load factor varies by architecture, 7/8 for ARM64&&LP64, and 3/4 for others

#if __arm__ || __x86_64__ || __i386__ #define CACHE_END_MARKER 1 static inline mask_t cache_fill_ratio(mask_t capacity) { return capacity * 3 / 4; } #elif __arm64__ && ! __LP64__ #define CACHE_END_MARKER 0 static inline mask_t cache_fill_ratio(mask_t capacity) { return capacity * 3 / 4; } #define CACHE_END_MARKER 0 static inline mask_t cache_fill_ratio(mask_t capacity) { return capacity * 7 / 8; }Copy the code

The value of freeOld indicates whether to reclaim old memory. The value is false when the method is inserted for the first time and true when the capacity is expanded. Call collect_free to clear and reclaim memory.

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) {
        collect_free(oldBuckets, oldCapacity);
    }
}

Copy the code

AllocateBuckets function When __arm__ | | __x86_64__ | | __i386__ CACHE_END_MARKER is 1, the rest is 0, when CACHE_END_MARKER in capacity – 1 to 1 place to insert a sel is 1, Imp is newBuckets (cache header) or newBuckets – 1, and CLS is nil endMarker

# if __arm__ | | __x86_64__ | | __i386__ / / omitted part of the code # define CACHE_END_MARKER 1 # elif __arm64__ &&! __LP64__ #define CACHE_END_MARKER 0 #elif __arm64__ && __LP64__ #define CACHE_END_MARKER 0 #endif #if CACHE_END_MARKER bucket_t *cache_t::endMarker(struct bucket_t *b, uint32_t cap) { return (bucket_t *)((uintptr_t)b + bytesForCapacity(cap)) - 1; } bucket_t *cache_t::allocateBuckets(mask_t newCapacity) { // Allocate one extra bucket to mark the end of the list. // This can't overflow mask_t because newCapacity is a power of 2. bucket_t *newBuckets = (bucket_t *)calloc(bytesForCapacity(newCapacity), 1); bucket_t *end = endMarker(newBuckets, newCapacity); #if __arm__ // End marker's sel is 1 and imp points BEFORE the first bucket. // This saves an instruction in objc_msgSend. end->set<NotAtomic, Raw>(newBuckets, (SEL)(uintptr_t)1, (IMP)(newBuckets - 1), nil); #else // End marker's sel is 1 and imp points to the first bucket. end->set<NotAtomic, Raw>(newBuckets, (SEL)(uintptr_t)1, (IMP)newBuckets, nil); #endif if (PrintCaches) recordNewCache(newCapacity); return newBuckets; } #else bucket_t *cache_t::allocateBuckets(mask_t newCapacity) { if (PrintCaches) recordNewCache(newCapacity); return (bucket_t *)calloc(bytesForCapacity(newCapacity), 1); Size_t ::bytesForCapacity(uint32_t cap) {return sizeof(bucket_t) * cap; }Copy the code

SetBucketsAndMask function

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED /* Deposit the first address of bucket_t * in _bucketsAndMaybeMask. Store newCapacity - 1mask to _maybeMask. _occupied is set to 0 because buckets have just been set and there is no real caching method. */ void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { // objc_msgSend uses mask and buckets with no locks. // It is safe for objc_msgSend to see new buckets  but old mask. // (It will get a cache miss but not overrun the buckets' bounds). // It is unsafe for objc_msgSend to see old buckets and new mask. // Therefore we write new buckets, wait a lot, then write new mask. // objc_msgSend reads mask first, then buckets. #ifdef __arm__ // ensure other threads see buckets contents before buckets pointer mega_barrier(); _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_relaxed); // ensure other threads see new buckets before new mask mega_barrier(); _maybeMask.store(newMask, memory_order_relaxed); _occupied = 0; #elif __x86_64__ || i386 // ensure other threads see buckets contents before buckets pointer _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release); // ensure other threads see new buckets before new mask _maybeMask.store(newMask, memory_order_release); _occupied = 0; #else #error Don't know how to do setBucketsAndMask on this architecture. #endif } #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 | | CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS bucket_t / / * Hash the first address into _bucketsAndMaybeMask. void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { uintptr_t buckets = (uintptr_t)newBuckets; uintptr_t mask = (uintptr_t)newMask; ASSERT(buckets <= bucketsMask); ASSERT(mask <= maxMask); _bucketsAndMaybeMask.store(((uintptr_t)newMask << maskShift) | (uintptr_t)newBuckets, memory_order_relaxed); _occupied = 0; } #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { uintptr_t buckets = (uintptr_t)newBuckets; unsigned mask = (unsigned)newMask; ASSERT(buckets == (buckets & bucketsMask)); ASSERT(mask <= 0xffff); _bucketsAndMaybeMask.store(buckets | objc::mask16ShiftBits(mask), memory_order_relaxed); _occupied = 0; ASSERT(this->buckets() == newBuckets); ASSERT(this->mask() == newMask); }Copy the code

Collect_free function parsing

Empty the contents of the memory address and reclaim the memory.

void cache_t::collect_free(bucket_t *data, mask_t capacity)
{
#if CONFIG_USE_CACHE_LOCK
    cacheUpdateLock.assertLocked();
#else
    runtimeLock.assertLocked();
#endif

    if (PrintCaches) recordDeadCache(capacity);

    _garbage_make_room ();
    garbage_byte_size += cache_t::bytesForCapacity(capacity);
    garbage_refs[garbage_count++] = data;
    cache_t::collectNolock(false);
}
Copy the code

Cache_hash Hash algorithm that computes memory bits

static inline mask_t cache_hash(SEL sel, mask_t mask) { uintptr_t value = (uintptr_t)sel; //sel #if CONFIG_USE_PREOPT_CACHES value ^= value >> 7; #endif return (mask_t)(value & mask); }Copy the code

The cache_next function resolves:

Hash algorithm, used for hash conflict, again calculate method insert position.

#if CACHE_END_MARKER
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}
#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

Copy the code

Bucket_t ‘ ‘set

Set sel, IMP for the bucket, and use IMP and CLS ^ to get the new newImp and save

void bucket_t::set(bucket_t *base, SEL newSel, IMP newImp, Class cls) { ASSERT(_sel.load(memory_order_relaxed) == 0 || _sel.load(memory_order_relaxed) == newSel); // objc_msgSend uses sel and imp with no locks. // It is safe for objc_msgSend to see new imp but NULL sel // (It will get a cache miss but not dispatch to the wrong place.) // It is unsafe for objc_msgSend to see old imp and new sel. // Therefore we write new imp, wait a lot, then write new sel. uintptr_t newIMP = (impEncoding == Encoded ? encodeImp(base, newImp, newSel, cls) : (uintptr_t)newImp); if (atomicity == Atomic) { _imp.store(newIMP, memory_order_relaxed); if (_sel.load(memory_order_relaxed) ! = newSel) { #ifdef __arm__ mega_barrier(); _sel.store(newSel, memory_order_relaxed); #elif __x86_64__ || __i386__ _sel.store(newSel, memory_order_release); #else #error Don't know how to do bucket_t::set on this architecture. #endif } } else { _imp.store(newIMP, memory_order_relaxed); _sel.store(newSel, memory_order_relaxed); } } uintptr_t encodeImp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, IMP newImp, UNUSED_WITHOUT_PTRAUTH 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(base, newSel, cls)); #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE return (uintptr_t)newImp; #else #error Unknown method cache IMP encoding. #endif }Copy the code

LLDB validationcache_tStructure (x86_64) :

@interface XQPerson : NSObject -(void)eatFood; -(void)watchMovie; -(void)drink; -(void)walk; -(void)run; -(void)playGame; -(void)playBasketBall; @end @implementation XQPerson -(void)eatFood{ NSLog(@"%s",__func__); } -(void)watchMovie{ NSLog(@"%s",__func__); } -(void)drink{ NSLog(@"%s",__func__); } -(void)walk{ NSLog(@"%s",__func__); } -(void)run{ NSLog(@"%s",__func__); } -(void)playGame{ NSLog(@"%s",__func__); } -(void)playBasketBall{ NSLog(@"%s",__func__); } @end int main(int argc, const char * argv[]) { @autoreleasepool { XQPerson* person = [XQPerson alloc]; NSLog(@"%@",person); } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * LLDB debugging result * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / print XQPerson memory information (LLDB) x / 4 g XQPerson. Class 0x1000082a0: 0x0000000100008278 0x000000010036e140 0x1000082b0: 0x0000000100366390 0x0000801000000000 // Memory Translation To Obtain the cache pointer (LLDB) p/x (cache_t*)(0x1000082A0 + 0x10) (cache_t*) $1 = 0x00000001000082B0 // * Cache (LLDB) p *$1 (cache_t) $2 = {_bucketsAndMaybeMask = {STD ::__1::atomic<unsigned Long > = {Value = 4298531728}} = {= {_maybeMask = {STD ::__1::atomic<unsigned int> = { Value = 0}} _flags = 32784 _occupied = 0} _originalPreoptCache = {STD ::__1::atomic<preopt_cache_t *> = {Value = 0x0000801000000000}}}} // First call method (LLDB) p [person eatFood] 2022-02-08 15:01:50.163459+0800 KCObjcBuild[28031:484830] -[XQPerson eatFood] (lldb) p *$1 (cache_t) $3 = { _bucketsAndMaybeMask = { std::__1::atomic<unsigned long> = { Value = 4314906912 } } = { = { _maybeMask = { std::__1::atomic<unsigned int> = { // After the first call to the method, the cache capacity should be 3, here it is 7, Value = 7}} _flags = 32784 _occupied = 1} _originalPreoptCache = { STD ::__1::atomic<preopt_cache_t *> = {Value = 0x0001801000000007}}}} // Second call method (LLDB) p [person walk] 2022-02-08 15:02:26.298207+0800 kqperson walk [28031:484830] -[XQPerson walk] (LLDB) p *$1 (cache_t) $4 = {_bucketsAndMaybeMask = { std::__1::atomic<unsigned long> = { Value = 4314906912 } } = { = { _maybeMask = { std::__1::atomic<unsigned int> = { Value = 7}} _flags = 32784 // The respondsToSelector method and the class method are automatically populated, _occupied = 4} _originalPreoptCache = {STD ::__1::atomic<preopt_cache_t *> = {Value = 0x0004801000000007}} $buckets() (bucket_t *) $5 = 0x0000000101304120 In turn to take out the bucket (LLDB) p $5 [0] (bucket_t) $6 = {_sel = {STD: : __1: : atomic < objc_selector * > = "" {Value =" "}} _imp = { STD ::__1::atomic<unsigned long> = {Value = 47360}}} sel(LLDB) p $6.sel() (sel) $7 = "walk" Imp (here the first parameter is not used because of architecture problems, Imp (nil, xqPerson.class) (IMP) $8 = 0x0000000100003ba0 (KCObjcBuild '-[XQPerson walk]) (LLDB) p $5[1] (bucket_t) $9 = { _sel = { std::__1::atomic<objc_selector *> = "" { Value = "" } } _imp = { std::__1::atomic<unsigned long> = { Value = 3525280 } } } (lldb) p $9.sel() (SEL) $10 = "class" (lldb) p $9.imp(nil,XQPerson.class) (IMP) $11 = 0x0000000100354800 (libobjc.A.dylib`-[NSObject class] at NSObject.mm:2243) (lldb)  p $5[2] (bucket_t) $12 = { _sel = { std::__1::atomic<objc_selector *> = (null) { Value = nil } } _imp = { STD ::__1::atomic<unsigned Long > = {Value = 0}}} (LLDB) p $12.sel() // Sel $13 = (null) (LLDB) p $5[3] (bucket_t) $14 = {_sel = {STD ::__1::atomic<objc_selector *> = (null) {Value = nil}} _imp = { std::__1::atomic<unsigned long> = { Value = 0 } } } (lldb) p $5[4] (bucket_t) $15 = { _sel = { std::__1::atomic<objc_selector *> = "" { Value = "" } } _imp = { std::__1::atomic<unsigned long> = { Value = 3525088 } }  } (lldb) p $15.sel() (SEL) $16 = "respondsToSelector:" (lldb) p $15.imp(nil,XQPerson.class) (IMP) $17 = 0x0000000100354b40 (libobjc.A.dylib`-[NSObject respondsToSelector:] at NSObject.mm:2299) (lldb) p $5[5] (bucket_t) $18 = {_sel = {STD ::__1::atomic<objc_selector *> = (null) {Value = nil}} _imp = {STD ::__1::atomic<objc_selector *> = null} std::__1::atomic<unsigned long> = { Value = 0 } } } (lldb) p $5[6] (bucket_t) $19 = { _sel = { std::__1::atomic<objc_selector *> = "" { Value = "" } } _imp = { std::__1::atomic<unsigned long> = { Value = 47536 } } }  (lldb) p $19.sel() (SEL) $20 = "eatFood" (lldb) p $19.imp(nil,XQPerson.class) (IMP) $21 = 0x0000000100003b10 (KCObjcBuild`-[XQPerson eatFood])Copy the code

The first time the person object calls eatFood, _occupied is 1; _maybeMask is 7 (it should be 3)(endMaker causes capacity to be 1 less than opened space) because LLDB automatically inserts respondsToSelector and class methods

Source code debugging

struct xq_bucket_t { SEL _sel; IMP _imp; }; struct xq_class_data_bits_t { uintptr_t bits; }; typedef uint32_t mask_t; Struct xq_bucket_t *_buckets {// the address of _bucketsAndMaybeMask refers to _buckets; // 8 mask_t _maybeMask; // 4 uint16_t _flags; // 2 uint16_t _occupied; / / 2}; struct xq_objc_class { Class isa; Class superclass; struct xq_cache_t cache; // formerly cache pointer and vtable struct xq_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags }; Void testCache(void){struct xq_objc_class *xq_class = (__bridge xq_objc_class *)(xqperson.class); NSLog(@"occupied = %hu - mask = %u", xq_class->cache._occupied, xq_class->cache._maybeMask); for (mask_t i = 0; i < xq_class->cache._maybeMask; i++) { struct xq_bucket_t bucket = xq_class->cache._buckets[i]; NSLog(@"%@ - %pf", NSStringFromSelector(bucket._sel), bucket._imp); } } int main(int argc, const char * argv[]) { @autoreleasepool { XQPerson* person = [XQPerson alloc]; [person eatFood]; [person walk]; testCache(); [person run]; [person watchMovie]; testCache(); [person playGame]; [person playBasketBall]; testCache(); NSLog(@"%@",person); } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LLDB debugging result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-08 16:22:23. 981992 + 0800 [34018:591588] -[XQPerson eatFood] 2022-02-08 16:22:23.982675+0800 -[XQPerson walk] 2022-02-08 16:22:23.982705+0800 cache[34018:591588] occupied = 2- mask = 3 2022-02-08 16:22:23.982731+0800 Cache [34018:591588] (NULL) -0x0f 2022-02-08 16:22:23.982778+0800 eatFood - 0xbaA0F [34018:591588] walk-0xBa30f 2022-02-08 16:22:23.982837+0800 [34018:591588] -[XQPerson Run] 2022-02-08 16:22:23.982866+0800 -[XQPerson watchMovie] 2022-02-08 16:22:23.982888+0800 source code cache[34018:591588] occupied = 2-mask = 7 2022-02-08 16:22:23.982908+0800 Cache [34018:591588] (NULL) -0x0F 2022-02-08 16:22:23.982925+0800 Cache [34018:591588] (NULL) -0x0f 2022-02-08 16:22:23.982942+0800 source code debug cache[34018:591588] (NULL) -0x0F 2022-02-08 16:22:23.982960+0800 source code debug cache[34018:591588] (NULL) -0x0F 2022-02-08 16:22:23.982979+0800 Cache [34018:591588] run-0xBde0f 2022-02-08 16:22:23.983014+0800 Cache [34018:591588] watchMovie - 0xBa90F 2022-02-08 16:22:23.983036+0800 Cache [34018:591588] (NULL) -0x0f 2022-02-08 16:22:23.983056+0800 cache[34018:591588] -[XQPerson playGame] 2022-02-08 16:22:23.983075+0800 [34018:591588] -[XQPerson playBasketBall] 2022-02-08 16:22:23.983092+0800 cache[34018:591588] occupied = [34018:591588] playGame - 0xbdd0f 2022-02-08 16:22:23.983148+0800 80F [34018:591588] playbasketball-0xBD80F 2022-02-08 16:22:24.011256+0800 80F Cache [34018:591588] (NULL) - 0x0F 2022-02-08 16:22:24.011294+0800 Cache [34018:591588] (NULL) - 0x0F 2022-02-08 16:22:24.011321+0800 Cache [34018:591588] run-0xbde0f 2022-02-08 16:22:24.011345+0800 Cache [34018:591588] watchMovie - 0xba90f 2022-02-08 16:22:24.011370+0800 Source code debug Cache [34018:591588] (NULL) -0x0FCopy the code

Imitation source code run results analysis:

  • Source code debugging does not automatically insert methods like LLDB does.

  • When the third method is inserted, insert is called, Mask_t newOccupied = occupied() + 1 = 3; mask_t newOccupied = occupied() + 1 = 3; mask_t newOccupied = occupied() + 1 = 3 The result 3 of mask () + 1 = 4 is compared and does not meet the conditions. Therefore, capacity expansion is required to clear the previous cache and save the last method.

Cache policy description:

Why empty the oldBuckets instead of expanding the space and then attaching a new cache?

A: The existing memory cannot be changed, so the expansion is a pseudo-expansion, that is, a new memory is created to replace the old memory. First, if you take out the old buckets’ cache and put it into a new bucket, it will cost performance and time. Second, Apple’s caching policy assumes that newer is better. For example, after method A is called once, the probability of being called again is very low. It is meaningless to keep the cache after capacity expansion. If method A is called again, it will be cached again until the next capacity expansion. Third, prevent the infinite number of cached methods, resulting in slow method lookup.

Cache insert flowchart