#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TDPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
-(void)sayHello;
- (void)sayCode;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
NS_ASSUME_NONNULL_END
#import "TDPerson.h"
@implementation TDPerson
- (void)sayHello{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayCode{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayMaster{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayNB{
NSLog(@"LGPerson say : %s",__func__);
}
+ (void)sayHappy{
NSLog(@"LGPerson say : %s",__func__);
}
@end
複製代碼
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
}
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__ //真機64位
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__ //真32位
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else //模擬器/mac
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
//模擬器/真機中的cache _buckets和_mask是分開的
//explicit_atomic 原子性,保證增刪改查時候的線程安全
//_buckets 查看源碼可知裏面存儲的是sel和imp
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
... //其餘都是寫掩碼 省略
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//真機將_mask和_buckets寫在一塊兒爲了節省空間
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
... //其餘都是寫掩碼 省略
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
... //其餘都是寫掩碼 省略
}
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
}
複製代碼
#import <Foundation/Foundation.h>
#import "TDPerson.h"
#import <objc/runtime.h>
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct td_bucket_t {
SEL _sel;
IMP _imp;
};
struct td_cache_t {
struct td_bucket_t * _buckets;
mask_t _mask;
uint16_t _flags;
uint16_t _occupied;
};
struct td_class_data_bits_t {
uintptr_t bits;
};
struct td_objc_class {
Class ISA;
Class superclass;
struct td_cache_t cache; // formerly cache pointer and vtable
struct td_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
TDPerson *p = [TDPerson alloc];
Class pClass = [TDPerson class]; // objc_clas
// [p say1];
// [p say2];
// [p say3];
[p say4];
struct td_objc_class *td_pClass = (__bridge struct td_objc_class *)(pClass);
NSLog(@"%hu - %u",td_pClass->cache._occupied,td_pClass->cache._mask);
for (mask_t i = 0; i<td_pClass->cache._mask; i++) {
// 打印獲取的 bucket
struct td_bucket_t bucket = td_pClass->cache._buckets[i];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
看運行結果打印_occupied 和 _mask,_sel,_imp: _mask是指掩碼數據,用於在哈希算法或者哈希衝突算法中計算哈希下標,其中mask 等於capacity - 1c++
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
//第一步計算newOccupied 這裏若果沒有init和屬性賦值(會有一個set方法)的狀況下而且是第一次調用方法則occupied() = 0 newOccupied = 1
mask_t newOccupied = occupied() + 1;
//第二步獲得當前容量
unsigned oldCapacity = capacity(), capacity = oldCapacity;
//slowpath小几率事件isConstantEmptyCache buckets是空的狀況下 建立緩存
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
//INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2), INIT_CACHE_SIZE_LOG2 =2
//其實就是初始化容量是4
if (!capacity) capacity = INIT_CACHE_SIZE;
//開闢空間
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4 3 + 1 bucket cache_t
// Cache is less than 3/4 full. Use it as-is.
//若是newOccupied + CACHE_END_MARKER = newOccupied + 1的數量小於總容量的3/4的話不須要作處理
//直接走下面的插入流程
}
else {
//大於總容量的3/4則進行擴容(原來容量的兩倍)
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 擴容兩倍 4
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true); // 內存 庫容完畢
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
//經過哈希計算bucket_t下標
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) {
//若是當前位置下存儲的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);
}
複製代碼
從這段代碼能夠知道方法插入計算下標的時候可能會出現哈希衝突,若是出現哈希衝突就須要從新計算方法的下標,因此在遍歷buckets的時候有時候方法打印的順序不必定就和方法調用的順序一致注意:init和屬性賦值也會插入cache,init自己就是一個方法,屬性賦值會觸發set方法因此也會存到cache 2. reallocate源碼分析 ALWAYS_INLINE
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets = buckets(); //獲得舊的buckets
bucket_t *newBuckets = allocateBuckets(newCapacity); //建立一個新的buckets,此時是個臨時的buckets
// 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);
//將臨時的buckets存到緩存中
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
//釋放老的bucket
/**
static void cache_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; //將傳入的buckets向後添加
cache_collect(false); //開始釋放
}
*/
cache_collect_free(oldBuckets, oldCapacity);
}
}
複製代碼
從這裏能夠發現當須要擴容的時候會將之前存儲的方法清理掉,因此上述調用到say3的時候你會發現say1和say2不見了 3. insert流程