OC底层原理之类的结构分析

3,571 阅读17分钟

前言

前文通过 Clang 编译main.m文件,得到class在底层实际是 struct objc_class* 的结构体指针,如下图:

image.png

objc4-818.2源码中全局搜索 struct objc_class,发现一共有三处定义:

1.Objc1.0objc_class

image.png

2.objc-runtime-old.h文件里objc_class的定义(已弃用)

image.png

3.objc-runtime-new.h文件里objc_class的定义

image.png

objc-runtime-new.h文件里objc_class可以看到,一共有4个成员变量,分别是从objc_object继承过来的isa,superClass,cache,bits。其中isa,superClass上文已经分析过了,本文不在赘述,本文先分析cachebits

内存平移

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

*************************打印结果***************************
2022-02-05 14:09:29.561329+0800 内存平移[1661:24500] 0x7ff7bfeff2b0 - 0x7ff7bfeff2b0 - 0x7ff7bfeff2b4
2022-02-05 14:09:29.561653+0800 内存平移[1661:24500] 0x7ff7bfeff2b0 - 0x7ff7bfeff2b4 - 0x7ff7bfeff2b8
2022-02-05 14:09:29.561689+0800 内存平移[1661:24500] 1
2022-02-05 14:09:29.561737+0800 内存平移[1661:24500] 1
2022-02-05 14:09:29.561777+0800 内存平移[1661:24500] 3
2022-02-05 14:09:29.561801+0800 内存平移[1661:24500] 4
2022-02-05 14:09:29.561823+0800 内存平移[1661:24500] 5

由上面的打印可知,连续的内存地址可以通过内存平移的方式得到后续的值,因此,已知一个类的内存地址,要获取此类的bits,可以内存平移的方式获取。

要拿到bits的数据需要从首地址平移,已知isasuperClass都是指针,各占8字节,因此只需要得到cache的字节大小就可以得到bits

查看 cache的结构,为一个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字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;               //4字节
#if __LP64__
            uint16_t                   _flags;                   //2字节
#endif
            uint16_t                   _occupied;                //2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;  //8字节
    };
    //联合体内存互斥,所以cache_t共占用16字节
    //省略static变量和方法(不占用结构体内存空间)
}

由上面分析可以计算出,cache_t共占用 16 个字节,所以从类的首地址平移32个字节即可得到 bits内的信息

bits分析

获取类的属性:

类的属性获取流程为: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

@end


******************************lldb调试流程******************************
//拿到XQPerson内存地址
(lldb) x/4g XQPerson.class                                                      
0x100008318: 0x00000001000082f0 0x000000010036e140
0x100008328: 0x0000000100366390 0x0000802400000000
//类的首地址即isa平移0x20个字节
(lldb) p/x 0x100008318 + 0x20
(long) $1 = 0x0000000100008338
//类型转换为class_data_bits_t*结构体指针
(lldb) p (class_data_bits_t*)$1
(class_data_bits_t *) $2 = 0x0000000100008338
//指针调用方法或属性属于->,结构体使用.
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101c0d500
//获取到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
    }
  }
}
//取出list
(lldb) p $4.list
(const RawPtr<property_list_t>) $5 = {
  ptr = 0x00000001000081f8
}
//取出ptr
(lldb) p $5.ptr
(property_list_t *const) $6 = 0x00000001000081f8
// 取出指针指向的值,一共有3个属性
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 3)
}
//取出属性
(lldb) 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")


获取类的成员变量(ivars)

成员变量的获取流程与获取属性略有不同,成员变量只存放在class_ro_t中。 成员变量(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;
    //省略部分代码
}


******************************lldb调试流程******************************

(lldb) x/4g 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 = 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
}


获取类的实例方法

方法的获取流程和属性类似,也是先获取到class_rw_t,然后调用methods()方法,最后获取到method_t,但是method_t结构体里面没有成员变量,所以直接输出会显示为空,需要获取method_t里面的big结构体。

类的实例方法获取流程为:XQPerson.class -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t -> method_t -> big(如没有使用small)。

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;
    }
    //省略部分代码
}

******************************lldb调试流程******************************

(lldb) x/4g 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:])
}


获取类方法与获取实例方法路径类似,只需先获取元类,再执行以上操作,这里就不列举了

获取类的协议

获取类的协议首先需要遵守一个协议

协议声明及遵守协议


@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

协议获取流程:XJPerson.class -> class_data_bits_t -> class_rw_t -> protocol_array_t -> protocol_list_t -> protocol_ref_t -> protocol_t -> method_list_t -> method_t -> bigbig没有就用small)。

protocol_list_tprotocol_ref_tprotocol_t部分源码:


//protocol_ref_t是 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通过list[0]得到protocol_ref_t,然后类型转换为(protocol_t*)结构体指针
    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;
    }
};

lldb调试流程


(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)
//得到protocol_ref_t
(lldb) p $6.list[0]
(protocol_ref_t) $7 = 4295002056
(lldb) p (protocol_t *)$7
//类型转换为 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
}
//获取协议中实例方法
(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 = 0x0000000000000000
}
//获取协议中类方法
(lldb) p $9.classMethods
(method_list_t *) $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
}

cache_t分析

cache_t部分源码解析


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

cache_t部分成员变量解析

_bucketsAndMaybeMask 根据不同架构决定存放不同的信息,X86_64存放buckets,arm64高16位存储 mask,低48位 buckets_maybeMask当前缓存空间的容量,arm64架构下不使用。_occupied当前缓存的方法个数。

X86_64架构下_bucketsAndMaybeMask验证:


(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


由lldb调试打印可以得出,3=3=4,所以_bucketsAndMaybeMask的指针指向buckets

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
// 省略函数
}

bucket_t结构体成员可以得出,cache_t存储的是方法。可以得出如下关系图:

Untitled Diagram.drawio (1).png

cache_t关键函数解析:

insert函数解析:


#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.
    // 获取当前已缓存方法个数(第一次为0),然后+1
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        // 重新开辟一块capacity * sizeof(bucket_t)大小的内存空间,
                // 将`bucket_t *`首地址存入`_bucketsAndMaybeMask`,
                // 将`newCapacity - 1`的`mask`存入`_maybeMask`,
                // _occupied设置为0
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    // 未超过容量的3/4 或 7/8(根据架构决定),正常使用
    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 {
        // 超过容量的3/4 或 7/8,两倍扩容
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        // 重新开辟一块capacity * sizeof(bucket_t)大小的内存空间,
        // 将`bucket_t *`首地址存入`_bucketsAndMaybeMask`,
        // 将`newCapacity - 1`的`mask`存入`_maybeMask`,
        // _occupied设置为0,回收旧内存
        reallocate(oldCapacity, capacity, true);
    }
    // 创建bucket_t
    bucket_t *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)) {// i处为空,可以插入
            // _occupied++,缓存的方法个数+1
            incrementOccupied();
            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
}

capacity函数解析,根据不同架构分别如下:

//计算当前容量
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;
}


capacity计算当前缓存总数

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

cache_fill_ratio,计算缓存占用最大容量,按架构不同,负载因子 不同,arm64 && LP64时为7/8,其余为 3/4

#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;
}
#elif __arm64__ && __LP64__// 真机满足此条件
#define CACHE_END_MARKER 0
static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 7 / 8;
}

reallocate解析: 开辟 sizeof(bucket_t) * capacity的内存空间,freeOld代表是否回收旧内存,第一次插入方法时为false,后续扩容时为true,调用collect_free函数清空、回收。

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);
    }
}

allocateBuckets函数解析: __arm__ || __x86_64__ || __i386__CACHE_END_MARKER为1,其余为0,当CACHE_END_MARKER为1时在 capacity-1 处插入一个sel为1,imp为newBuckets(缓存区首地址)或者newBuckets - 1cls为nil的 endMarker标记


#if __arm__  ||  __x86_64__  ||  __i386__
//省略了部分代码
#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);
}

#endif

//计算需要开辟的内存
size_t cache_t::bytesForCapacity(uint32_t cap)
{
    return sizeof(bucket_t) * cap;
}

setBucketsAndMask函数解析:

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED

/*将bucket_t *首地址存入_bucketsAndMaybeMask。
将newCapacity - 1mask存入_maybeMask。
_occupied设为0,因为刚刚设置buckets,还没有真正缓存方法。
*/
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 *首地址哈希后存入_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);
}

collect_free函数解析

将传入的内存地址的内容清空,回收内存。

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);
}

cache_hash哈希算法,算出存储位


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);
}

cache_next函数解析:

再哈希算法,用于哈希冲突后,再次计算方法插入的位置。

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

bucket_t``set函数解析:

bucket设置sel,imp,并用impcls进行^,得到新的newImp并保存

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 // 真机执行,使用imp^cls
        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
    }


lldb验证cache_t结构(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调试结果****************************
// 打印XQPerson类的内存信息
(lldb) x/4g XQPerson.class
0x1000082a0: 0x0000000100008278 0x000000010036e140
0x1000082b0: 0x0000000100366390 0x0000801000000000
// 内存平移得到cache的指针
(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> = {
        //未调用方法,容量为0
          Value = 0
        }
      }
      _flags = 32784
      // 缓存个数为0
      _occupied = 0
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0000801000000000
      }
    }
  }
}
//第一次调用方法
(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> = {
        // 首次调用方法后,缓存容量应该是3,此处是7,是由于lldb会自动插入 respondsToSelector方法和 class方法导致的扩容
          Value = 7
        }
      }
      _flags = 32784
      _occupied = 1
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0001801000000007
      }
    }
  }
}
// 第二次调用方法
(lldb) p [person walk]
2022-02-08 15:02:26.298207+0800 KCObjcBuild[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
      //调用两个方法后,自动填充了respondsToSelector方法和class方法,一共缓存了4个方法
      _occupied = 4
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0004801000000007
      }
    }
  }
}
// 取出cache的buckets
(lldb) p $4.buckets()
(bucket_t *) $5 = 0x0000000101304120
//内存平移,依次取出bucket
(lldb) p $5[0]
(bucket_t) $6 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 47360
    }
  }
}
//调用sel方法,取出sel
(lldb) p $6.sel()
(SEL) $7 = "walk"
//调用imp方法,取出imp(此处由于架构问题,第一个参数未使用,所以可以传nil)
(lldb) p $6.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<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])


lldb调试结果分析,person对象第一次调用方法eatFood后,_occupied1_maybeMask7(实际应该为3)(有endMaker导致容量比开辟的空间少1),原因是lldb自动插入了respondsToSelector方法和class方法

仿源码调试

struct xq_bucket_t {
    SEL _sel;
    IMP _imp;
};

struct xq_class_data_bits_t {
    uintptr_t bits;
};

typedef uint32_t mask_t;
struct xq_cache_t {
    //由于前文分析过_bucketsAndMaybeMask的地址指向_buckets,所以此处可以直接按此方式声明
    struct xq_bucket_t *_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 struct 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调试结果*************************************

2022-02-08 16:22:23.981992+0800 仿源码调试cache[34018:591588] -[XQPerson eatFood]
2022-02-08 16:22:23.982675+0800 仿源码调试cache[34018:591588] -[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 仿源码调试cache[34018:591588] eatFood - 0xbaa0f
2022-02-08 16:22:23.982809+0800 仿源码调试cache[34018:591588] walk - 0xba30f
2022-02-08 16:22:23.982837+0800 仿源码调试cache[34018:591588] -[XQPerson run]
2022-02-08 16:22:23.982866+0800 仿源码调试cache[34018:591588] -[XQPerson watchMovie]
2022-02-08 16:22:23.982888+0800 仿源码调试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 仿源码调试cache[34018:591588] (null) - 0x0f
2022-02-08 16:22:23.982960+0800 仿源码调试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 仿源码调试cache[34018:591588] -[XQPerson playBasketBall]
2022-02-08 16:22:23.983092+0800 仿源码调试cache[34018:591588] occupied = 4 - mask = 7
2022-02-08 16:22:23.983111+0800 仿源码调试cache[34018:591588] playGame - 0xbdd0f
2022-02-08 16:22:23.983148+0800 仿源码调试cache[34018:591588] playBasketBall - 0xbd80f
2022-02-08 16:22:24.011256+0800 仿源码调试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 仿源码调试cache[34018:591588] (null) - 0x0f

仿源码运行结果分析:

  • 仿照源码调试不会想lldb那样自动插入方法。

  • 当插入第三个方法时,调用insert函数,mask_t newOccupied = occupied() + 1 = 3加上CACHE_END_MARKER(此时为1)的结果4cache_fill_ratio(capacity)capacity = oldCapacity = mask() + 1 = 4)的结果3进行比较,不满足条件,需要进行扩容,清空之前的缓存并保存最后一个方法。

缓存策略说明:

为什么要清空oldBuckets,而不是空间扩容,然后在后面附加新的缓存呢?

解答:已经开辟的内存无法更改,这里的扩容其实是伪扩容,是开辟了一块新的内存,替代了原来的旧内存,之所以使用这种方式,第一,如果将旧buckets的缓存都拿出来,放入新的buckets,耗费性能和时间;第二,苹果缓存策略认为越新越好。举例说明,A方法被调用了一次之后,被继续调用的概率极低,扩容之后仍然保持缓存没有意义,如果再次调用A方法,会再一次进行缓存,直到下一次扩容之前;第三,防止缓存的方法无限增多,导致方法查找缓慢。

cache insert流程图:

Untitled Diagram.drawio.png