isa
對于大家來說應(yīng)該并不陌生,不管是各個公司的面試題或者說是平時的開發(fā)當(dāng)中都會經(jīng)常被提及,另外在我們之前對alloc
的源碼分析時也發(fā)現(xiàn),最后一步obj->initInstanceIsa(cls, hasCxxDtor)
便是對isa
的初始化.今天我們就跟隨源碼一起來看一看isa
到底是個怎樣的存在
聯(lián)合體位域
在講isa
之前,我們先來學(xué)習(xí)一個概念:聯(lián)合體位域.
聯(lián)合體與結(jié)構(gòu)體
結(jié)構(gòu)體(struct)中所有變量是"共存"的--優(yōu)點是"有容乃大",全面;缺點是內(nèi)存空間的分配是粗放的,不管用不用,全分配
聯(lián)合體(union)中各變量是"互斥"的--缺點是不夠"包容";優(yōu)點是內(nèi)存使用更為精細(xì)靈活,節(jié)省了內(nèi)存空間
舉例說明:
存在如下四個屬性
@property (nonatomic, assign) int front; // 1:正在向前 2:沒有向前
@property (nonatomic, assign) int back; // 1:正在向后 2:沒有向后
@property (nonatomic, assign) int left; // 1:正在向左 2:沒有向左
@property (nonatomic, assign) int right; // 1:正在向右 2:沒有向右
如上,每個int占用四個字節(jié),總共占用4*4=16
個字節(jié),共16*8=128
位,造成了大部分內(nèi)存浪費,
而用聯(lián)合體位域表現(xiàn)如下:
// 聯(lián)合體
union {
char bits; // 占用1位,8個字節(jié),二進(jìn)制表示為0b00000000
// 位域
struct { // 0000 1111
char front : 1; // 占用bits的第一個字節(jié)
char back : 1; // 占用bits的第二個字節(jié)
char left : 1; // 占用bits的第三個字節(jié)
char right : 1; // 占用bits的第四個字節(jié)
};
} _direction;
#define LYDirectionFrontMask (1 << 0)
#define LYDirectionBackMask (1 << 1)
#define LYDirectionLeftMask (1 << 2)
#define LYDirectionRightMask (1 << 3)n
// 初始化
_direction.bits = 0b0000000000;
// 賦值
_direction.bits |= LYDirectionFrontMask;
// 取值
return _direction.front;
isa源碼定義
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
};
ISA_BITFIELD定義如下:
// arm64架構(gòu)下
# 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
// x86_64架構(gòu)
# 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
通過源碼我們可以發(fā)現(xiàn)isa
采用了聯(lián)合體的結(jié)構(gòu).arm64
架構(gòu)與x86_64
架構(gòu)主要區(qū)別在shiftcls
和extra_rc
占用的長度.
isa存儲內(nèi)容
接下來我們一起看下isa
中定義的各個字段具體指什么(arm64
架構(gòu)下)
nonpointer
(存儲在第0字節(jié))是否為優(yōu)化isa
標(biāo)志。0代表是優(yōu)化前的isa
,一個純指向類或元類的指針;1表示優(yōu)化后的isa
立哑,不止是一個指針挑庶,isa
中包含類信息来候、對象的引用計數(shù)等⌒独現(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)存(dealloc
的底層代碼有體現(xiàn))揪利。has_cxx_dtor
(存儲在第2個字節(jié))析構(gòu)函數(shù)標(biāo)志位,如果有析構(gòu)函數(shù)狠持,則需進(jìn)行析構(gòu)邏輯疟位,如果沒有,則可以更快速地釋放對象(dealloc
的底層代碼有體現(xiàn))喘垂。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ù)是否過大蜕径,如果過大則需要其他散列表來進(jìn)行存儲两踏。extra_rc
(存儲在第45-63字節(jié)。)存放該對象的引用計數(shù)值減1后的結(jié)果兜喻。對象的引用計數(shù)超過 1梦染,會存在這個里面,如果引用計數(shù)為 10,extra_rc
的值就為 9
由上我們可以知道shiftcls
中存儲了類的相關(guān)信息,接下來我們通過源碼來驗證下
// isa初始化
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
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;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
通過代碼我們可以發(fā)現(xiàn)所謂的isa
和對象進(jìn)行關(guān)聯(lián)就是將cls
的信息存儲在新的isa
的shiftcls
上,因為類信息的存儲是從第四位開始的,所以需要將cls
右移3位,即:newisa.shiftcls = (uintptr_t)cls >> 3;
我們再來看下newisa
賦值前后的值分別是什么
// 賦值前,即執(zhí)行newisa.shiftcls = (uintptr_t)cls >> 3;前
(lldb) p newisa
(isa_t) $4 = {
cls = 0x001d800000000001
bits = 8303511812964353
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
// 賦值后
(lldb) p newisa
(isa_t) $5 = {
cls = LYPerson
bits = 8303516107940081
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 536871966
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
通過賦值前后的對比我們發(fā)現(xiàn)新的isa
的cls
變?yōu)榱?code>LYPerson,shiftcls
也由0變成了536871966.由此也證明了isa
與對象關(guān)聯(lián)就是將類信息存入到isa的shiftcls
中
除此之外,我們還可以通過移位來證明,因為arm64
下shiftcls
占用3~35共33位,所以我們可以通過先右移3位,再左移30位,最后再右移27位來獲取shiftcls
的值,如下:
(lldb) x/4gx p
0x10201f950: 0x001d8001000024dd 0x0000000000000000
0x10201f960: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d8001000024dd >> 3
1037939513492635
(lldb) po 1037939513492635 << 30
562951189692416
(lldb) po 562951189692416 >> 27
Person
(lldb)
當(dāng)然,對于左移右移我們還可以通過位運(yùn)算&
來實現(xiàn),如下
# define ISA_MASK 0x0000000ffffffff8ULL
(lldb) po 0x001d8001000024dd & 0x0000000ffffffff8ULL
Person
至此,我們對isa
的底層源碼以及存儲的值信息已經(jīng)有了一定的了解,以后遇到關(guān)于isa
的相關(guān)問題也能回答的更自信一些了.