runtime 基礎知識
runtime
是執行時,在執行的時候做一些事請,可以動態新增類和交換函式,那麼有一個基礎知識需要了解,arm64架構前,isa指標是普通指標,儲存class和meta-class物件的記憶體地址,從arm64架構開始,對isa進行了優化,變成了一個union
共用體,還是用位域來儲存更多的資訊,我們首先看一下isa指標的結構:
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
//****
}
#include "isa.h"
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
複製程式碼
objc_object
是結構體,包含了私有屬性isa_t
,isa_t isa
是一個共用體,包含了ISA_BITFIELD
是一個巨集(結構體),bits
是uintptr_t
型別,uintptr_t
其實是unsign long
型別佔用8位元組,就是64位,我們進入到ISA_BITFIELD
內部:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
複製程式碼
ISA_BITFIELD
在arm64
和x86
是兩種結構,儲存了nonpointer
,has_assoc
,has_cxx_dtor
,shiftcls
,magic
,weakly_referenced
,deallocating
,has_sidetable_rc
,extra_rc
這些資訊,:1
就佔用了一位,:44
就是佔用了44位,:6
就是佔用了6位,:8
就是佔用了8位,那麼共用體isa_t
簡化之後
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
};
};
複製程式碼
isa_t
是使用共用體結構,使用bits
儲存了結構體的資料,那麼共用體是如何使用的?我們來探究一下
共用體基礎知識
首先我們定義一個FYPerson
,新增2個屬性
@interface FYPerson : NSObject
@property (nonatomic,assign) BOOL rich;
@property (nonatomic,assign) BOOL tell;
@property (nonatomic,assign) BOOL handsome;
@end
複製程式碼
然後檢視該類的例項佔用空間大小
FYPerson *p=[[FYPerson alloc]init];
p.handsome = YES;
p.rich = NO;
NSLog(@"大小:%zu",class_getInstanceSize(FYPerson.class));
//16
複製程式碼
FYPerson
定義了三個屬性,佔用空間是16位元組,那麼我們換一種方法實現這個三個屬性的功能。
我們定義6個方法,3個set方法,3個get方法。
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
//實現:
//使用0b00000000不是很易讀,我們換成下邊的寫法1<<0
//#define FYHandsomeMask 0b00000001
//#define FYTallMask 0b00000010
//#define FYRichMask 0b00000001
#define FYHandsomeMask (1<<0)
#define FYTallMask (1<<1)
#define FYRichMask (1<<2)
@interface FYPerson()
{
char _richTellHandsome;//0000 0000 rich tall handsome
}
@end
@implementation FYPerson
- (void)setRich:(BOOL)tall{
if (tall) {
_richTellHandsome = _richTellHandsome|FYRichMask;
}else{
_richTellHandsome = _richTellHandsome&~FYRichMask;
}
}
- (void)setTall:(BOOL)tall{
if (tall) {
_richTellHandsome = _richTellHandsome|FYTallMask;
}else{
_richTellHandsome = _richTellHandsome&~FYTallMask;
}
}
- (void)setHandsome:(BOOL)tall{
if (tall) {
_richTellHandsome = _richTellHandsome|FYHandsomeMask;
}else{
_richTellHandsome = _richTellHandsome&~FYHandsomeMask;
}
}
- (BOOL)isRich{
return !!(_richTellHandsome&FYRichMask);
}
- (BOOL)isTall{
return !!(_richTellHandsome&FYTallMask);
}
- (BOOL)isHandsome{
return !!(_richTellHandsome&FYHandsomeMask);
}
@end
複製程式碼
我們定義了一個char型別的變數_richTellHandsome
,4位元組,32位,可以儲存32個bool型別的變數。賦值是使用_richTellHandsome = _richTellHandsome|FYRichMask
,或_richTellHandsome = _richTellHandsome&~FYRichMask
,取值是!!(_richTellHandsome&FYRichMask)
,前邊加!!
是轉化成bool
型別的,否則取值出來是1 or 2 or 4
。我們再換一種思路將三個變數定義成一個結構體,取值和賦值都是可以直接操作的。
@interface FYPerson()
{
// char _richTellHandsome;//0000 0000 rich tall handsome
//位域
struct{
char tall : 1;//高度
char rich : 1;//富有
char handsome : 1; //帥
} _richTellHandsome; // 0b0000 0000
//使用2位 yes就是0b01 轉化成1位元組8位就是:0o0101 0101 結果是1
//使用1位 yes就是0b1 轉化成1位元組8位就是:0o1111 1111 所以結果是-1
}
@end
@implementation FYPerson
- (void)setRich:(BOOL)tall{
_richTellHandsome.rich = tall;
}
- (void)setTall:(BOOL)tall{
_richTellHandsome.tall = tall;
}
- (void)setHandsome:(BOOL)tall{
_richTellHandsome.handsome = tall;
}
- (BOOL)isRich{
return !!_richTellHandsome.rich;
}
- (BOOL)isTall{
return !!_richTellHandsome.tall;
}
- (BOOL)isHandsome{
return !!_richTellHandsome.handsome;
}
@end
複製程式碼
結構體_richTellHandsome
包含三個變數char tall : 1;
,char rich : 1;
,char handsome : 1
。每一個變數佔用空間為1位,3個變數佔用3位。取值的時候使用!!(_richTellHandsome&FYHandsomeMask)
,賦值使用
if (tall) {
_richTellHandsome = _richTellHandsome|FYHandsomeMask;
}else{
_richTellHandsome = _richTellHandsome&~FYHandsomeMask
}
複製程式碼
我們採用位域來儲存資訊, 位域是指資訊在儲存時,並不需要佔用一個完整的位元組, 而只需佔幾個或一個二進位制位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。為了節省儲存空間,並使處理簡便,C語言又提供了一種資料結構,稱為“位域”或“位段”。所謂“位域”是把一個位元組中的二進位劃分為幾 個不同的區域, 並說明每個區域的位數。每個域有一個域名,允許在程式中按域名進行操作。 這樣就可以把幾個不同的物件用一個位元組的二進位制位域來表示。
另外一個省空間的思路是使用聯合
,
使用union
,可以更省空間,“聯合”是一種特殊的類,也是一種構造型別的資料結構。在一個“聯合”內可以定義多種不同的資料型別, 一個被說明為該“聯合”型別的變數中,允許裝入該“聯合”所定義的任何一種資料,這些資料共享同一段記憶體,以達到節省空間的目的(還有一個節省空間的型別:位域)。 這是一個非常特殊的地方,也是聯合的特徵。另外,同struct一樣,聯合預設訪問許可權也是公有的,並且,也具有成員函式。
@interface FYPerson()
{
union {
char bits; //一個位元組8位 ricH /tall/handsome都是佔用的bits的記憶體空間
struct{
char tall : 1;//高度
char rich : 1;//富有
char handsome : 1; //帥
}; // 0b0000 0000
}_richTellHandsome;
}
@end
@implementation FYPerson
- (void)setRich:(BOOL)tall{
if (tall) {
_richTellHandsome.bits |= FYRichMask;
}else{
_richTellHandsome.bits &= ~FYRichMask;
}
}
- (void)setTall:(BOOL)tall{
if (tall) {
_richTellHandsome.bits |= FYTallMask;
}else{
_richTellHandsome.bits &= ~FYTallMask;
}
}
- (void)setHandsome:(BOOL)tall{
if (tall) {
_richTellHandsome.bits |= FYHandsomeMask;
}else{
_richTellHandsome.bits &= ~FYHandsomeMask;
}
}
- (BOOL)isRich{
return !!(_richTellHandsome.bits & FYRichMask);
}
- (BOOL)isTall{
return !!(_richTellHandsome.bits & FYTallMask);
}
- (BOOL)isHandsome{
return (_richTellHandsome.bits & FYHandsomeMask);
}
複製程式碼
使用聯合
共用體,達到省空間的目的,runtime
原始碼中是用來很多union
和位運算。
例如KVO 的NSKeyValueObservingOptions
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions){
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,
NSKeyValueObservingOptionInitial = 0x04,
NSKeyValueObservingOptionPrior = 0x08
}
複製程式碼
這個NSKeyValueObservingOptions
使用位域,當傳進去的時候NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
,則傳進去的值為0x3
,轉化成二進位制就是0b11
,則兩位都是1
可以包含2個值。
那麼我們來設計一個簡單的可以使用或來傳值的列舉
typedef enum {
FYOne = 1,// 0b 0001
FYTwo = 2,// 0b 0010
FYTHree = 4,//0b 0100
FYFour = 8,// 0b 1000
}FYOptions;
- (void)setOptions:(FYOptions )ops{
if (ops &FYOne) {
NSLog(@"FYOne is show");
}
if (ops &FYTwo) {
NSLog(@"FYTwo is show");
}
if (ops &FYTHree) {
NSLog(@"FYTHree is show");
}
if (ops &FYFour) {
NSLog(@"FYFour is show");
}
}
[self setOptions:FYOne|FYTwo|FYTHree];
//輸出是:
FYOne is show
FYTwo is show
FYTHree is show
複製程式碼
這是一個名字為FYOptions
的列舉,第一個是十進位制是1,二進位制是0b 0001
,第二個十進位制是2,二進位制是0b 0010
,第三個十進位制是4,二進位制是0b 0100
,第四個十進位制是8,二進位制是0b 1000
。
那麼我們使用的時候可以FYOne|FYTwo|FYTHree
,打包成一個值,相當於1|2|4 = 7
,二進位制表示是0b0111
,後三位都是1,可以通過&mask取出對應的每一位的數值。
Class的結構
isa詳解 – 位域儲存的資料及其含義
引數 | 含義 |
---|---|
nonpointer | 0->代表普通的指標,儲存著Class、Meta-Class物件的記憶體地址。1->代表優化過,使用位域儲存更多的資訊 |
has_assoc | 是否有設定過關聯物件,如果沒有,釋放時會更快 |
has_cxx_dtor | 是否有C++的解構函式(.cxx_destruct),如果沒有,釋放時會更快 |
shiftcls | 儲存著Class、Meta-Class物件的記憶體地址資訊 |
magic | 用於在除錯時分辨物件是否未完成初始化 |
weakly_referenced | 是否有被弱引用指向過,如果沒有,釋放時會更快 |
deallocating | 物件是否正在釋放 |
extra_rc | 裡面儲存的值是引用計數器減1 |
has_sidetable_rc | 引用計數器是否過大無法儲存在isa中 |
如果為1,那麼引用計數會儲存在一個叫SideTable的類的屬性中 |
class結構
struct fy_objc_class : xx_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
fy_objc_class* metaClass() { // 提供metaClass函式,獲取元類物件
// 上一篇我們講解過,isa指標需要經過一次 & ISA_MASK操作之後才得到真正的地址
return (fy_objc_class *)((long long)isa & ISA_MASK);
}
};
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;
};
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;
};
複製程式碼
class_ro_t
是隻讀的,class_rw_t
是讀寫的,在原始碼中runtime
->Source
->objc-runtime-new.mm
->static Class realizeClass(Class cls) 1869行
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
//如果已註冊 就返回
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
//只讀ro
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();//初始化ro
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
//初始化 rw
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
//指標指向rw 一開始是指向ro的
cls->setData(rw);
}
isMeta = ro->flags & RO_META;
rw->version = isMeta ? 7 : 0; // old runtime went up to 6
複製程式碼
開始cls->data
指向的是ro
,初始化之後,指向的rw
,rw->ro
指向的是原來的ro
。
class_rw_t
中的method_array_t
是儲存的方法列表,我們進入到method_array_t
看下它的資料結構:
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
typedef list_array_tt<method_t, method_list_t> Super;
public:
method_list_t **beginCategoryMethodLists() {
return beginLists();
}
method_list_t **endCategoryMethodLists(Class cls);
method_array_t duplicate() {
return Super::duplicate<method_array_t>();
}
};
複製程式碼
method_array_t
是一個類,儲存了method_t
二維陣列,那麼我們看下method_t
的結構
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
複製程式碼
method_t
是儲存了3個變數的結構體,SEL
是方法名,types
是編碼(方法返回型別,引數型別), imp
函式指標(函式地址)。
SEL
- SEL代表方法\函式名,一般叫做選擇器,底層結構跟char *類似
- 可以通過@selector()和sel_registerName()獲得
- 可以通過sel_getName()和NSStringFromSelector()轉成字串
- 不同類中相同名字的方法,所對應的方法選擇器是相同的
Type Encoding
iOS中提供了一個叫做@encode的指令,可以將具體的型別轉成字元編碼,官方網站外掛encodeing
code | Meaning |
---|---|
c | A char |
i | An int |
s | A short |
l | A long |
l | is treated as a 32-bit quantity on 64-bit programs. |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
我們通過一個例子來了解encode
-(void)test:(int)age heiht:(float)height{
}
FYPerson *p=[[FYPerson alloc]init];
SEL sel = @selector(test:heiht:);
Method m1= class_getInstanceMethod(p.class, sel);
const char *type = method_getTypeEncoding(m1);
NSLog(@"%s",type);
//輸出
v24@0:8i16f20
//0id 8 SEL 16 int 20 float = 24
複製程式碼
v24@0:8i16f20
是encoding的值,我們來分解一下,前邊是v24
是函式返回值是void
,所有引數佔用了24
位元組,@0:8
是從第0開始,長度是8位元組的位置,i16
是從16位元組開始的int
型別,f20
是從20位元組開始,型別是float
。
方法快取
Class內部結構中有個方法快取(cache_t),用雜湊表(雜湊表)來快取曾經呼叫過的方法,可以提高方法的查詢速度。
我們來到cache_t
內部
struct cache_t {
struct bucket_t *_buckets;//雜湊表
mask_t _mask;//雜湊表長度-1
mask_t _occupied;//已經儲存的方法數量
}
struct bucket_t {
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;//SEL作為key
MethodCacheIMP _imp; //函式地址
#endif
}
複製程式碼
雜湊表的資料結構表格所示
索引 | bucket_t |
---|---|
0 | bucket_t(_key,_imp) |
1 | bucket_t(_key,_imp) |
2 | bucket_t(_key,_imp) |
3 | bucket_t(_key,_imp) |
4 | bucket_t(_key,_imp) |
... | ... |
通過cache_getImp(cls, sel)
獲取IMP
。具體在cache_t::find
函式中
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
//key&mask 得到索引
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} 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);
}
// Class points to cache. SEL is key. Cache buckets store SEL+IMP.
// Caches are never built in the dyld shared cache.
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
複製程式碼
首先獲取buckets()
獲取butket_t
,然後獲取_mask
,通過
cache_hash(k, m)
獲取第一次訪問的索引i
,cache_hash
通過(mask_t)(key & mask)
得出具體的索引
,當第一次成功獲取到butket_t
則直接返回,否則執行cache_next(i, m)
獲取下一個索引,直到獲取到或者迴圈一遍結束。
那麼我們來驗證一下已經執行的函式的確是存在cache中的,我們自定義了class_rw_t
#import <Foundation/Foundation.h>
#ifndef MJClassInfo_h
#define MJClassInfo_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
複製程式碼
測試程式碼是
FYPerson *p = [[FYPerson alloc]init];
Method test1Method = class_getInstanceMethod(p.class, @selector(test));
Method test2Method = class_getInstanceMethod(p.class, @selector(test2));
IMP imp1= method_getImplementation(test1Method);
IMP imp2= method_getImplementation(test2Method);
mj_objc_class *cls = (__bridge mj_objc_class *)p.class;
NSLog(@"-----");
[p test];
[p test2];
cache_t cache = cls->cache;
bucket_t *buck = cache._buckets;
for (int i = 0; i <= cache._mask; i ++) {
bucket_t item = buck[i];
if (item._key != 0) {
NSLog(@"key:%lu imp:%p",item._key,item._imp);
}
}
//輸出
p imp1
(IMP) $0 = 0x0000000100000df0 (day11-runtime1`-[FYPerson test] at FYPerson.m:12)
(lldb) p imp2
(IMP) $1 = 0x0000000100000e20 (day11-runtime1`-[FYPerson test2] at FYPerson.m:15)
p/d @selector(test) //輸出 test方法的sel地址
(SEL) $6 = 140734025103231 "test"
(lldb) p/d @selector(test2) //輸出 test2方法的sel地址
(SEL) $7 = 4294971267 "test2"
key1:140733954181041 imp1:0x7fff59fc4cd1
key2:4294971267 imp2:0x100000e20 //對應test2
key3:140734025103231 imp3:0x100000df0 //對應test1
複製程式碼
可以看出來IMP1
和IMP2
、key1
和key2
分別對應了bucket_t
中的key2
,key3
和imp2
和imp3
。
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
//當initialized 沒有執行完畢的時候不快取
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache <= 3/4
}
else {
擴容 之後,快取清空
cache->expand();
}
//bucket_t 最小是4,當>3/4時候,擴容,空間擴容之後是之前的2️倍。
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
複製程式碼
cache_t
初始化是大小是4,當大於3/4時,進行擴容,擴容之後是之前的2倍,資料被清空,cacha->_occupied
恢復為0。
驗證程式碼如下:
FYPerson *p = [[FYPerson alloc]init];
mj_objc_class *cls = (__bridge mj_objc_class *)p.class;
NSLog(@"-----");
[p test];
/*
key:init imp:0x7fff58807c2d
key:class imp:0x7fff588084b7
key:(null) imp:0x0
key:test imp:0x100000bf0
Program ended with exit code: 0
*/
[p test2]; //當執行該函式的時候
/*
key:(null) imp:0x0
key:(null) imp:0x0
key:(null) imp:0x0
key:(null) imp:0x0
key:(null) imp:0x0
key:(null) imp:0x0
key:test2 imp:0x100000c20
key:(null) imp:0x0
*/
cache_t cache = cls->cache;
bucket_t *buck = cache._buckets;
for (int i = 0; i <= cache._mask; i ++) {
bucket_t item = buck[i];
// if (item._key != 0) {
//// printf("key:%s imp:%p \n",(const char *)item._key,item._imp);
// }
printf("key:%s imp:%p \n",(const char *)item._key,item._imp);
}
複製程式碼
總結
- arm64之後isa使用聯合體用更少的空間儲存更多的資料,arm64之前儲存class和meta-class指標。
- 函式執行會先從cache中查詢,沒有的話,當再次找到該函式會新增到cache中
- 從
class->cache
查詢bucket_t
的key需要先&_mask
之後再判斷是否有該key
- cache擴容在大於3/4進行2倍擴容,擴容之後,舊資料刪除,
imp
個數清空 class->rw
在初始化中講class_ro_t
值賦值給rw
,然後rw->ro
指向之前的ro
。
資料下載
最怕一生碌碌無為,還安慰自己平凡可貴。
廣告時間