從源碼objc_runtime-new.h 中可以看到
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; //方法緩存
class_data_bits_t bits; //用於獲取具體的類信息
class_rw_t *data() {
return bits.data();
}
}
// 我們來看 bits*data 這個方法的實現
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// bits & FAST_DATA_MASK 返回一個class_rw_t 的結構體指針
接着我們來窺探下 class_rw_t 都存儲來哪些東西?
class_rw_t裏面的methods、properties、protocols是二維數組,是可讀可寫的,包含了類的初始內容、分類的內容
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;//方法列表
property_array_t properties; //屬性列表
protocol_array_t protocols;//協議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
//class_ro_t 如下
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance對象的size
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //類名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; //成員變量
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}
class_ro_t裏面的baseMethodList、baseProtocols、ivars、baseProperties是一維數組,是隻讀的,包含了類的初始內容。
然後我來在來看看method_t 吧。
struct method_t {
SEL name; //方法選擇器(方法名)
const char *types; //編碼(返回值類型,參數類型)
/*
ios提供了一個@encode的指令,可以將具體的類型變成字符串編碼。
假如有這樣一個方法:
-(int)test:(int)Age Height:(CGFloat)height;
type 爲:i24@:08i16f20
其中i爲返回值類型,24 所有參數的字節總數(id,SEL,int,float),@是id,:代表SEL,i參數int,f參數float.另外的數字代表參數 開始的字節位數
*/
IMP imp;//方法的實現
}
SEL代表方法\函數名,一般叫做選擇器,底層結構跟char *類似,想要驗證也很簡單。如下:
char *str = "init";
NSLog(@"%s-%@",str,NSStringFromSelector(@selector(init)));
//打印如下
2018-10-31 15:52:10.218499+0800 關聯對象[3274:207551] init-init
可以通過sel_getName()和NSStringFromSelector()轉成字符串
不同類中相同名字的方法,所對應的方法選擇器是相同的
繼續往下看:Class內部結構中有個方法緩存(cache_t),用散列表(哈希表)來緩存曾經調用過的方法,可以提高方法的查找速度。(很重要)
struct cache_t {
struct bucket_t *_buckets; //散列表
mask_t _mask; //散列表的長度-1
mask_t _occupied;//已經緩存的方法數量
}
struct bucket_t {
private:
cache_key_t _key; // SEL作爲key
IMP _imp; //函數的內存地址,可理解爲value
}
//簡述下原理:對象調用方法是通過isa 是找到類對象,
然後在類對象通過方法緩存去找到對應的方法緩存列表_buckets,
一旦發現調用的SEL與緩存列表中的key 相同,就指出取出函數的地址,
通過地址直接調用函數方法。
原理如下:
0 | NULL |
1 | NULL |
2 | NULL |
4 | bucket_t (_key=@select(test),_imp) |
...... | ..... |
假如散列表如上,對象的方法名叫test。他會拿對象方法的地址 & _mask(上面介紹過了是散列表的長度-1) 假如爲4,他就會去座標爲4的散列表找方法,(直接去找座標爲4的方法列表,前面的列表位置留空)假如方法存在直接調用,假如不存在就直接緩存進去。假如兩個不同的地址值 & _mask得出來的索引值相同,那會怎麼做吶?來看下源碼
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
/*
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
*/
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
/*
cache_next 的實現
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
*/
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
發現相同之後,吧得到的下標-1 緩存到得到的下標裏去。如果直接減到0的時候直接賦值mask,然後在-1 慢慢的找,直到找到空餘的位置緩存。以空間換時間。大家可以自己參考objc_cache.mm文件中。
//一個類的內部結構-供參考:一個c++文件
// XZClassInfo.h
// TestClass
#import <Foundation/Foundation.h>
#ifndef XZClassInfo_h
#define XZClassInfo_h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
#else
#error unknown architecture
#endif
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
struct cache_t {
bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
IMP imp(SEL selector)
{
mask_t begin = _mask & (long long)selector;
mask_t i = begin;
do {
if (_buckets[i]._key == 0 || _buckets[i]._key == (long long)selector) {
return _buckets[i]._imp;
}
} while ((i = cache_next(i, _mask)) != begin);
return NULL;
}
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
struct method_t {
SEL name;
const char *types;
IMP imp;
};
struct method_list_t : entsize_list_tt {
method_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
ivar_t first;
};
struct property_t {
const char *name;
const char *attributes;
};
struct property_list_t : entsize_list_tt {
property_t first;
};
struct chained_property_list {
chained_property_list *next;
uint32_t count;
property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
uintptr_t count;
protocol_ref_t list[0];
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance對象佔用的內存空間
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 類名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t * methods; // 方法列表
property_list_t *properties; // 屬性列表
const protocol_list_t * protocols; // 協議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
/* OC對象 */
struct mj_objc_object {
void *isa;
};
/* 類對象 */
struct mj_objc_class : mj_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
mj_objc_class* metaClass() {
return (mj_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* XZClassInfo_h */
用法如下:
mj_objc_class *goodStuderntClass = (__bridge mj_objc_class *)[XZGoodStudent class];
cache_t cache = goodStuderntClass->cache;
bucket_t *buckets = goodStuderntClass->cache._buckets;
bucket_t bucket =buckets [(long long)@selector(goodStudentTest) & cache._mask];
NSLog(@"%s",bucket._key);