前言
前文通过 Clang
编译main.m
文件,得到class
在底层实际是 struct objc_class*
的结构体指针,如下图:
在objc4-818.2
源码中全局搜索 struct objc_class
,发现一共有三处定义:
1.Objc1.0
的objc_class
2.objc-runtime-old.h
文件里objc_class
的定义(已弃用)
3.objc-runtime-new.h
文件里objc_class
的定义
从objc-runtime-new.h
文件里objc_class
可以看到,一共有4个成员变量,分别是从objc_object
继承过来的isa
,superClass
,cache
,bits
。其中isa
,superClass
上文已经分析过了,本文不在赘述,本文先分析cache
和bits
内存平移
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
的数据需要从首地址平移,已知isa
和 superClass
都是指针,各占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
-> big
(big
没有就用small
)。
protocol_list_t
、protocol_ref_t
、protocol_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调试打印可以得出,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
存储的是方法。可以得出如下关系图:
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 - 1
,cls
为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
,并用imp
和cls
进行^,得到新的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
后,_occupied
为1
,_maybeMask
为7
(实际应该为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
)的结果4
与cache_fill_ratio(capacity)
(capacity
=oldCapacity = mask() + 1 = 4
)的结果3
进行比较,不满足条件,需要进行扩容,清空之前的缓存并保存最后一个方法。
缓存策略说明:
为什么要清空oldBuckets,而不是空间扩容,然后在后面附加新的缓存呢?
解答:已经开辟的内存无法更改,这里的扩容其实是伪扩容,是开辟了一块新的内存,替代了原来的旧内存,之所以使用这种方式,第一,如果将旧buckets的缓存都拿出来,放入新的buckets,耗费性能和时间;第二,苹果缓存策略认为越新越好。举例说明,A方法被调用了一次之后,被继续调用的概率极低,扩容之后仍然保持缓存没有意义,如果再次调用A方法,会再一次进行缓存,直到下一次扩容之前;第三,防止缓存的方法无限增多,导致方法查找缓慢。