iOS底層原理 runtime-object_class拾遺基礎篇--(6)

runtime 基礎知識

runtime是運行時,在運行的時候作一些事請,能夠動態添加類和交換函數,那麼有一個基礎知識須要瞭解,arm64架構前,isa指針是普通指針,存儲class和meta-class對象的內存地址,從arm64架構開始,對isa進行了優化,變成了一個union共用體,仍是用位域來存儲更多的信息,咱們首先看一下isa指針的結構:html

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是一個宏(結構體),bitsuintptr_t類型,uintptr_t實際上是unsign long類型佔用8字節,就是64位,咱們進入到ISA_BITFIELD內部:git

# 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_BITFIELDarm64x86是兩種結構,存儲了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簡化以後github

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
複製代碼

而後查看該類的實例佔用空間大小bash

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),賦值使用app

if (tall) {
		_richTellHandsome = _richTellHandsome|FYHandsomeMask;
	}else{
		_richTellHandsome = _richTellHandsome&~FYHandsomeMask
	}
複製代碼

咱們採用位域來存儲信息, 位域是指信息在存儲時,並不須要佔用一個完整的字節, 而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位便可。爲了節省存儲空間,並使處理簡便,C語言又提供了一種數據結構,稱爲「位域」或「位段」。所謂「位域」是把一個字節中的二進位劃分爲幾 個不一樣的區域, 並說明每一個區域的位數。每一個域有一個域名,容許在程序中按域名進行操做。 這樣就能夠把幾個不一樣的對象用一個字節的二進制位域來表示。less

另一個省空間的思路是使用聯合, 使用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指向的是原來的roclass_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)獲取第一次訪問的索引icache_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
複製代碼

能夠看出來IMP1IMP2key1key2分別對應了bucket_t中的key2,key3imp2imp3

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

資料下載


最怕一輩子碌碌無爲,還安慰本身平凡難得。

廣告時間

相關文章
相關標籤/搜索