在分析類的時候,objc_class
結構體裏面有個 cache_t cache
成員變量。
一、cache 存的是什麼
先看下 cache 裏面存的是什麼
1.1 源碼查看
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maskZeroBits = 4;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
public:
static bucket_t *emptyBuckets();
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
// 省略很多方法...
}
發現比較重要的成員 _buckets
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
// Compute the ptrauth signing modifier from &_imp, newSel, and cls.
uintptr_t modifierForSEL(SEL newSel, Class cls) const {
return (uintptr_t)&_imp ^ (uintptr_t)newSel ^ (uintptr_t)cls;
}
// Sign newImp, with &_imp, newSel, and cls as modifiers.
uintptr_t encodeImp(IMP newImp, SEL newSel, Class cls) const {
if (!newImp) return 0;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
return (uintptr_t)
ptrauth_auth_and_resign(newImp,
ptrauth_key_function_pointer, 0,
ptrauth_key_process_dependent_code,
modifierForSEL(newSel, cls));
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (uintptr_t)newImp ^ (uintptr_t)cls;
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (uintptr_t)newImp;
#else
#error Unknown method cache IMP encoding.
#endif
}
public:
inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
inline IMP imp(Class cls) const {
uintptr_t imp = _imp.load(memory_order::memory_order_relaxed);
if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
SEL sel = _sel.load(memory_order::memory_order_relaxed);
return (IMP)
ptrauth_auth_and_resign((const void *)imp,
ptrauth_key_process_dependent_code,
modifierForSEL(sel, cls),
ptrauth_key_function_pointer, 0);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (IMP)(imp ^ (uintptr_t)cls);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (IMP)imp;
#else
#error Unknown method cache IMP encoding.
#endif
}
template <Atomicity, IMPEncoding>
void set(SEL newSel, IMP newImp, Class cls);
};
bucket_t 裏面就2個成員變量
-
_imp
: 方法實現 -
_sel
:方法指針
1.2 打印cache_t
實踐代碼:
@interface GLPerson : NSObject
@property (nonatomic, strong) NSString *name; /**< 8個字節 */
@property (nonatomic, strong) NSString *nickName; /**< 8個字節 */
- (void)sayHello1;
- (void)sayHello2;
- (void)sayHello3;
- (void)sayHello4;
@end
---
GLPerson *p = [GLPerson alloc];
[p sayHello1];
[p sayHello2];
[p sayHello3];
[p sayHello4];
斷點打印 [GLPerson class]
的內存,然後通過內存偏移找到 cache_t
。
在分析類的時候,知道 cache_t
偏移 16
字節。所以可通過如下調試打印出 cache_t
。
第一次我先在調用第一個實例方法之前打斷點,[p sayHello1];
打個斷點,然後打印:
(lldb) x/4gx GLPerson.class
0x1000022f8: 0x00000001000022d0 0x0000000100334140
0x100002308: 0x000000010032e440 0x0000802400000000
(lldb) p 0x1000022f8 + 0x10
(long) $1 = 4294976264
(lldb) p (cache_t *)$1
(cache_t *) $2 = 0x0000000100002308
(lldb) p *$2
(cache_t) $3 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x000000010032e440 {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 0
}
_flags = 32804
_occupied = 0
}
然後調用 [p sayHello1]
之後:
(lldb) p *$2
(cache_t) $4 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x0000000101c8e800 {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 3
}
_flags = 32804
_occupied = 1
}
再往下調用
// 調用 sayHello2 之後
(lldb) p *$2
(cache_t) $6 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x0000000101c8e800 {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 12200
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 3
}
_flags = 32804
_occupied = 2
}
// 調用完4個方法
(lldb) p *$2
(cache_t) $7 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x0000000101a0c160 {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 7
}
_flags = 32804
_occupied = 2
}
可以看出調用方法之後,類的 cache_t
成員變量變化了。
1.3 打印方法名
再回到調用第一個方法之後
(lldb) p *$1 // 打印cache_t
(cache_t) $3 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x0000000100645710 {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 11824
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 3
}
_flags = 32804
_occupied = 1
}
(lldb) p $3.buckets()
(bucket_t *) $4 = 0x0000000100645710
(lldb) p *$4
(bucket_t) $5 = {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 11824
}
}
(lldb) p $5.sel() // bucket_t的sel()方法
(SEL) $6 = "sayHello1"
(lldb) p $5.imp(pClass) // bucket_t的imp(Class)方法
(IMP) $7 = 0x0000000100000d30 (KCObjc`-[GLPerson sayHello1])
總結:
- 先得到
(cache_t *)$1
; -
p *$1
得到(cache_t) $3
; - 調用
cache_t
的buckets
方法,p $3.buckets()
,得到(bucket_t *) $4
; -
p *$4
得到(bucket_t) $5
; - 調用
bucket_t
的sel()
方法,打印出(SEL) $6 = "sayHello1"
SEL
名稱; - 調用
bucket_t
的imp(Class cls)
方法,打印出(IMP) $7 = 0x0000000100000d30 (-[GLPerson sayHello1])
IMP
實現地址;
然後調用完四個方法之後再打印:
(lldb) p $9.buckets()
(bucket_t *) $10 = 0x0000000101a07960
(lldb) p *$10
(bucket_t) $11 = {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
(lldb) p $11.sel()
(SEL) $12 = <no value available>
(lldb) p *($10+1)
(bucket_t) $13 = {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
(lldb) p *($10+2)
(bucket_t) $14 = {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
(lldb) p *($10+3)
(bucket_t) $15 = {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
(lldb) p *($10+4)
(bucket_t) $16 = {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 11856
}
}
(lldb) p $15.sel()
(SEL) $17 = <no value available>
(lldb) p $16.sel()
(SEL) $18 = "sayHello3"
(lldb) p *($10+5)
(bucket_t) $19 = {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
(lldb) p *($10+6)
(bucket_t) $20 = {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 11872
}
}
(lldb) p $19.sel()
(SEL) $21 = <no value available>
(lldb) p $20.sel()
(SEL) $22 = "sayHello4"
// 再往下繼續加,不是null就是其他東西了
可以知道,_buckets裏面就是存已經調用的方法的,調用完四個方法之後,發現這個_buckets裏面存的方法卻不是按順序排的,而是隨機的,中間可能還爲空。
二、cache 是怎麼存的
上面在打印 cache_t
的時候,發現有個變量_occupied是不是也在改變,還有 _mask
變量。
2.1 _occupied追蹤
objc
源碼全局搜索一下 _occupied
,發現用到的地方很少,其中只有下面一個方法能讓 _occupied
增加。
void cache_t::incrementOccupied()
{
_occupied++;
}
只有 cache_t
的 incrementOccupied
方法可增加 _occupied
的值。
再搜索 incrementOccupied
的調用地方,
只有 cache_t
的 insert
方法調用 incrementOccupied
。
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();
#if !DEBUG_TASK_THREADS
// Never cache before +initialize is done
if (cls->isInitialized()) {
cache_t *cache = getCache(cls);
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
cache->insert(cls, sel, imp, receiver);
}
#else
_collecting_in_critical();
#endif
}
只有 cache_fill
方法調用 cache->insert
。
再網上追溯,就脫離 cache
了,通過打斷點可見調用方法
發送方法以後再分析,先繼續關注 cache_t
。
2.2 cache->insert 存方法
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
#if CONFIG_USE_CACHE_LOCK
cacheUpdateLock.assertLocked();
#else
runtimeLock.assertLocked();
#endif
ASSERT(sel != 0 && cls->isInitialized());
// Use the cache as-is if it is less than 3/4 full
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;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
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 because the
// minimum size is 4 and we resized at 3/4 full.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
// 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));
cache_t::bad_cache(receiver, (SEL)sel, cls);
}
bool cache_t::isConstantEmptyCache()
{
return
occupied() == 0 &&
buckets() == emptyBucketsForCapacity(capacity(), false);
}
---
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
return (mask_t)(uintptr_t)sel & mask;
}
---
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
---
enum {
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),
MAX_CACHE_SIZE_LOG2 = 16,
MAX_CACHE_SIZE = (1 << MAX_CACHE_SIZE_LOG2),
};
---
#define CACHE_END_MARKER 1
流程分析:
- 賦值
newOccupied = occupied() + 1,oldCapacity = capacity = capacity();
; - 判斷
cache
是否爲空,爲空的話釋放舊的,開闢新的大小,新的爲4; -
newOccupied + CACHE_END_MARKER <= capacity / 4 * 3
,如果occupied + 1
之後還小於等於occupied
的 3/4 的話,就還用他自己; - 最後就是超過了 3/4 之後,會做 2 倍擴容,
capacity * 2
, 但是最大不超過 216; - 聲明一個
buckets()
數組,m = capacity - 1
,begin = cache_hash(sel, m)
,i = begin
; - 加入
m = 3
, 可知任何一個數&
3 得到的肯定小於等於3,也就是begin
肯定會小於capacity
,capacity
既可以理解爲buckets
的邊界大小; - do while 循環中,判斷
b[i].sel()
是否存在; - 等於0,說明不存在,調用
incrementOccupied
使_occupied++
,b[i].set<Atomic, Encoded>(sel, imp, cls)
緩存方法,結束
; - 存在並且等於要存的方法,說明在其它線程已經存了,
結束
; -
b[i].sel()
既不等於0,也不等於要存的方法; -
(i = cache_next(i, m)) != begin)
, 把i = (i + 1) & m
,判斷i
是否等於begin
,如果等於,重複步驟11
,如果不等於回到步驟7
;
三、取方法
在 objc-runtime-new.mm
文件中 log_and_fill_cache
方法的下面,有個 IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
方法。裏面有個cache_getImp
方法
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
通過 _cache_getImp
搜索,有各個架構的定義,下面是 arm64架構
的。
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LCacheMiss$1 which forcefully
// jumps to the cache-miss codepath.
//
// It is assumed that the CacheMiss codepath starts right at the end
// of CacheLookup2 and will re-setup the registers to meet the cache-miss
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting r9 to 0)
//
// NORMAL and STRET:
// - r0 or r1 (STRET) contains the receiver
// - r1 or r2 (STRET) contains the selector
// - r9 contains the isa (reloaded from r0/r1)
// - other registers are set as per calling conventions
//
LLookupStart$1:
ldrh r12, [r9, #CACHE_MASK] // r12 = mask
ldr r9, [r9, #CACHE] // r9 = buckets
.if $0 == STRET
and r12, r12, r2 // r12 = index = SEL & mask
.else
and r12, r12, r1 // r12 = index = SEL & mask
.endif
add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8
ldr r12, [r9, #CACHED_SEL] // r12 = bucket->sel
6:
.if $0 == STRET
teq r12, r2
.else
teq r12, r1
.endif
bne 8f
ldr r12, [r9, #CACHED_IMP] // r12 = bucket->imp
.if $0 == STRET
tst r12, r12 // set ne for stret forwarding
.else
// eq already set for nonstret forwarding by `teq` above
.endif
.endmacro
.macro CacheLookup2
#if CACHED_SEL != 0
# error this code requires that SEL be at offset 0
#endif
8:
cmp r12, #1
blo LCacheMiss$1 // if (bucket->sel == 0) cache miss
it eq // if (bucket->sel == 1) cache wrap
ldreq r9, [r9, #CACHED_IMP] // bucket->imp is before first bucket
ldr r12, [r9, #8]! // r12 = (++bucket)->sel
b 6b
LLookupEnd$1:
LCacheMiss$1:
.endmacro
彙編代碼,看右邊註釋就行。