稍微精深一點的IOS開發(fā)都聽說過isa指針。它在OC的類中起到了指示自身類型的作用,是runtime實現(xiàn)的基礎(chǔ)。那么isa指針到底是如何實現(xiàn)的呢,讓我們從源碼的層面進行分析。
NSObject -> Class -> objc_class -> objc_object
新建一個最簡單的空類:
@interface Person : NSObject
@end
@implementation Person
@end
command點擊NSObejct,我們可以看到cls的實現(xiàn):
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
那么這個Class又是什么數(shù)據(jù)呢?
typedef struct objc_class *Class;
我們可以看到Class實際上是objc_class結(jié)構(gòu)體的指針。而objc_class,就是OC類的元類。
objc_class在objc源碼中有三處定義:
// in runtime.h
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
.....
} OBJC2_UNAVAILABLE;
#if __OBJC2__
#include "objc-runtime-new.h"
#else
#include "objc-runtime-old.h"
#endif
// in objc-runtime-old.h
struct objc_class : objc_object {}
// in objc-runtime-new.h
struct objc_class : objc_object {}
我們當(dāng)前使用的版本多是 objc2,所以起作用的是 最后一個 objc-runtime-new.h中定義的objc_class。objc_class則繼承了objc_object。
struct objc_class : objc_object
objc_object同樣在兩個位置有定義:
in objc.h
#if !OBJC_TYPES_DEFINED
....
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
#endif
in objc-private.h
#ifdef _OBJC_OBJC_H_
#error include objc-private.h before other headers
#endif
#define OBJC_TYPES_DEFINED 1
struct objc_object {
private:
isa_t isa;
.....
}
根據(jù)objc-private.h中的宏定義我們可以看到,起作用的實際上是objc-private.h中的定義。
對一個繼承了NSObject的類而言,isa最后能定位到objc_object中。而用來存儲類信息的,是objc_object的 isa_t isa;
isa_t
** isa_t是一個union**聚合體。聚合體和結(jié)構(gòu)體最大的不同是它的內(nèi)存存儲方式。對聚合體而言,所有的成員變量的起始地址相同,對每一個成員的修改都會影響所有的變量。這樣做最大的好處就是可以節(jié)約內(nèi)存空間。但是也會導(dǎo)致每次可用的變量只有一個。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; // 初始化是不會使用的
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // 這才是關(guān)鍵
};
#endif
};

isa_t 是一個聯(lián)合體,這里所占空間為 8字節(jié),共64位 ,內(nèi)存布局從低位到高位情況如下圖:
struct {
ISA_BITFIELD; // 這才是關(guān)鍵
};
# 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
注意 在 struct 的變量A后面加 :(數(shù)字n), 是指定該變量A在內(nèi)存中占用 n 位。

nonpointer(存儲在第0字節(jié)) : 是否為優(yōu)化isa標(biāo)志。0代表是優(yōu)化前的isa,一個純指向類或元類的指針;1表示優(yōu)化后的isa,不止是一個指針,isa中包含類信息、對象的引用計數(shù)等?,F(xiàn)在基本上都是優(yōu)化后的isa。
has_assoc (存儲在第1個字節(jié)): 關(guān)聯(lián)對象標(biāo)志位。對象含有或者曾經(jīng)含有關(guān)聯(lián)引用,0表示沒有,1表示有,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存。
has_cxx_dtor(存儲在第2個字節(jié)): 析構(gòu)函數(shù)標(biāo)志位,如果有析構(gòu)函數(shù),則需進行析構(gòu)邏輯,如果沒有,則可以更快速地釋放對象。
shiftcls :(存儲在第3-35字節(jié))存儲類的指針,其實就是優(yōu)化之前 isa 指向的內(nèi)容。在arm64架構(gòu)中有33位用來存儲類指針。x86_64架構(gòu)有44位。
magic(存儲在第36-41字節(jié)):判斷對象是否初始化完成, 是調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間。
weakly_referenced(存儲在第42字節(jié)):對象被指向或者曾經(jīng)指向一個 ARC 的弱變量,沒有弱引用的對象可以更快釋放(dealloc的底層代碼有體現(xiàn))。
deallocating(存儲在第43字節(jié)):標(biāo)志對象是否正在釋放內(nèi)存。
has_sidetable_rc(存儲在第44字節(jié)):判斷該對象的引用計數(shù)是否過大,如果過大則需要其他散列表來進行存儲。
- extra_rc(存儲在第45-63字節(jié)。):存放該對象的引用計數(shù)值減1后的結(jié)果。對象的引用計數(shù)超過 1,會存在這個里面,如果引用計數(shù)為 10,extra_rc 的值就為 9。
isa的初始化
對OC類而言,isa的初始化是在 alloc中完成的。具體來說是在

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls,...) {
...
id obj; // id 是objc_object的指針
// 3: ?
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
obj->initIsa(cls);
}
...
}
initInstanceIsa與initIsa最后都會統(tǒng)一到一個函數(shù)里:
(去除一些宏定義,斷言以及條件判斷等,我們直接將代碼減少到它執(zhí)行的代碼)
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
- 首先對整個bits進行賦值,傳入 ISA_MAGIC_VALUE ,在arm64架構(gòu)下,該值為
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
將該值轉(zhuǎn)換為2進制

對應(yīng) isa_t 中內(nèi)存布局的位置,可以看出對 bits 賦值 就是對 nonpinter 和 magic 賦值的過程。
2.其次對has_cxx_dtor賦值。
- 最后對shifcls賦值
shiftcls詳解
shiftcls 里面存放著類的指針。眾所周知指針是64位的,那么為什么可以放置在33位或或者44位的shiftcls中呢?在需要指針時又如何還原呢?
首先我們看下shiftcls的賦值:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
....
isa_t newisa(0);
....
newisa.shiftcls = (uintptr_t)cls >> 3;
....
isa = newisa;
}
}
首先我們將cls的尾部三位去掉,這是因為對類而言,它至少包含一個isa指針,那么它的長度一定是8的整數(shù)倍。這里我們可以使用runtime計算下Person的長度來認(rèn)證下:
#import <objc/runtime.h>
size_t insSize = class_getInstanceSize([Person class]);
NSLog(@"PP Size:%zd",insSize);
然后,我們會看到源碼中shiftcls的宏定義后面有一個數(shù)據(jù) MACH_VM_MAX_ADDRESS:
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
這是最大虛擬尋址空間,也就是內(nèi)存空間能夠分配的最大地址。以ARM32為例,它最大的虛擬尋址空間為 0x10,0000,0000。而33 + 3 (右移的三位) = 36 = 4 * 9。正好是虛擬尋址空間的位數(shù) - 1。正好可以覆蓋虛擬尋址空間。也就是說64位的數(shù)據(jù)中,前面64 - 36 = 18位是無用的。
因此,只要存儲64位指針中間的33位就可以表示了。只要在使用的時候與上mask:0x0000000ffffffff8,就可以得到對應(yīng)的類指針。