isa_t作用
在OC的類結構中出現的第一個成員變量就是聯合體isa_t
struct objc_object {
private:
isa_t isa;
//在早期的版本中isa只是一個Class類型的指針
//Class _Nonnull isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
...
}
在早期的32bit版本中isa就是一個單一的指針,用於存儲當前對象的類或者類的元類. 但是在64bit爲操作系統上,用一個8字節指針的長度只存儲一個對象地址顯然是浪費的(操作系統只有一部分地址是可用於存儲對象地址的空間),所以apple對這個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
};
作爲過渡也爲了兼容早期的實現版本,這個結構中保存了變量Class class的實現,同時增加了uintptr_t(unsigned long)類型的變量bits,但由於使用的是聯合體(公用體,共用變量空間),所以該結構只佔用一個指針的空間.當使用bits變量進行存儲時,利用位域結構將變量的各個位進行拆分賦予不同的含義,充分利用了內存空間.
利用位域使得變量內不僅僅保存了指針值,同時還保存了很多有用的信息.
# 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
以主流的arm64爲例,主要包含了:
- nonpointer:佔用1bit,標識是否開啓isa優化.如果是一個指針值該位爲0,則表示當前結構的值只是一個指針沒有保存其他信息;如果爲1,則表示當前結構不是指針,而是一個包含了其他信息的位域結構;
- has_assoc:當前對象是否使用objc_setAssociatedObject動態綁定了額外的屬性;
- has_cxx_dtor: 是否含有C++或者OC的析構函數,不包含析構函數時對象釋放速度會更快;
- shiftcls: 這個值相當於早期實現中的isa指針,是真實的指針值,在arm64處理器上只佔據33位,可見其實在內存中可以用來存儲對象指針的空間是很有限的;
- magic:用於判斷對象是否已經完成了初始化,在 arm64 中 0x16 是調試器判斷當前對象是真的對象還是沒有初始化的空間(在 x86_64 中該值爲 0x3b);
- weakly_referenced:是否是弱引用對象;
deallocating
:對象是否正在執行析構函數(是否在釋放內存);has_sidetable_rc
:判斷是否需要用sidetable去處理引用計數;extra_rc
:存儲該對象的引用計數值減一後的結果. 當對象的引用計數使用extra_rc足以存儲時has_sidetable_rc=0;當對象的引用計數使用extra_rc不能存儲時has_sidetable_rc=1.可見對象的引用計數主要存儲在兩個地方:如果isa中extra_rc足以存儲則存儲在isa的位域中;如果isa位域不足以存儲,就會使用sidetable去存儲.
isa_t初始化
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
/*
此時省略了SUPPORT_INDEXED_ISA預編譯實現,這部分可以忽略,iOS中不使用這一技術
*/
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;
// 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;
}
}
在這一初始化過程中:
- newisa.bits = ISA_MAGIC_VALUE;首先使用ISA_MAGIC_VALUE將bits進行初始化,主要有兩個作用:
- 將nonpointer標記爲1,證明開啓了isa優化;
- 初始化magic位,用於標記該對象已經初始化.
- newisa.has_cxx_dtor = hasCxxDtor;初始化對象標記是否有C++或者OC析構函數,如果存在在對象釋放時需要消耗更多時間來對對象進行析構;
- newisa.shiftcls = (uintptr_t)cls >> 3;將對象指向的類或者類的元類指針賦值給位域shiftcls,因爲前三位被nonpointer,has_assoc和has_cxx_dtor被佔據,所以需要將真實的指針左移三位進行復制保存.
ISA_MASK
在iOS 64bit的操作系統中,爲了屏蔽外界對於isa指針的直接獲取對外部隱藏了真實的isa指針的值,而是需要使用ISA_MASK進行與操作來獲取到真實的isa.
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
所以如果想要通過獲取到對象真實的isa指針指向需要與ISA_MASK進行與操作:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# else
# error unknown architecture for packed isa
# endif
NSObject *obj = [[NSObject alloc] init];
void *ptr = (__bridge void *)obj;//牆紙轉化爲指針
void *isa = *(void **)ptr; //獲取類中第一個成員變量isa_t值
isa = (void *)((uintptr_t)isa & ISA_MASK); //獲取到isa真實的指針地址
id isaObj = (__bridge id)isa;//強轉爲id,即可獲取到isa指向的真實對象
NSLog(@"isaObj == %@", isaObj);
該過程也可以通過LLDB實現:
(lldb) po (void *)obj
<NSObject: 0x10110f400>
(lldb) x/2g 0x10110f400
0x10110f400: 0x001d800100b38141 0x0000000000000000
(lldb) p/x (0x001d800100b38141 & ISA_MASK) //根據需要替換ISA_MASK
(unsigned long long) $1 = 0x0000000100b38140
(lldb) po $1
NSObject
ISA_MAGIC_MASK
從isa_t位域的定義中可以知道,magic變量佔據了6bit的空間,使用ISA_MAGIC_MASK就可以快速地從isa_t中獲取到magic值.
在__arm64__中,位域magic佔據了從37位(1(nonpointer)+1(has_assoc)+1(has_cxx_dtor)+33(shiftcls) )開始的6bit空間,而ISA_MAGIC_MASK=0x000003f000000001ULL,除第37-42位和最低位外所有位均爲0;在__x86_64__中,位域magic佔據了從47位(1(nonpointer)+1(has_assoc)+1(has_cxx_dtor)+44(shiftcls))開始的6bit空間,而ISA_MAGIC_MASK=0x001f800000000001ULL,除第44-49位和最低位外其餘位均爲0.所以使用isa.bits & ISA_MAGIC_MASK,可以快速判斷出是否開啓nonpointer優化和magic的值.
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define MAGIC_MASK 36
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define MAGIC_MASK 47
# else
# error unknown architecture for packed isa
# endif
NSObject *obj = [[NSObject alloc] init];
void *ptr = (__bridge void *)obj;
ptr = *(void **)ptr;
uintptr_t magic = (uintptr_t)(void *)((uintptr_t)ptr & ISA_MAGIC_MASK);
if (magic & 1) {
NSLog(@"magic == %lu", magic >> MAGIC_MASK);
}