一贮聂、對象的本質
main.m
文件
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface FXPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation FXPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
首先橙弱,我們使用終端先跳轉到把main.m
的根目錄圣贸,把main.m
文件使用clang編譯命令轉為cpp
文件宝当,會得到下面main.cpp
文件
clang -rewrite-objc main.m -o main.cpp
main.cpp
文件
typedef struct objc_object FXPerson;
typedef struct {} _objc_exc_FXPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_FXPerson$_name;
struct FXPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
從main.cpp
文件中我們可以看到對象的本質其實都是結構體腮恩。而結構體當中的struct NSObject_IMPL NSObject_IVARS
其實對應的就是我們的isa指針
侯嘀。
二彤灶、聯(lián)合體惰许、位域
@interface FXCar : NSObject
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
// 對象 - 屬性
// 1*4 = 4字節(jié)*8位 = 32位 浪費
- (void)setFront:(BOOL)isFront; // 存儲 : 1字節(jié) = 8位 0000 1111 char + 位域 bit 結構體
- (BOOL)isFront;
- (void)setBack:(BOOL)isBack;
- (BOOL)isBack;
@end
假設我們一個對象(FXCar)
里面有4個屬性(front倔监、back直砂、left、right)
浩习,每個BOOL屬性占1個字節(jié)静暂,4個屬性就占4個字節(jié)(32位數(shù)據)。其實只需要一個字節(jié)后面4位的內容就可以存儲這4個屬性谱秽。
結構體(struct)
中所有變量是“共存”的
- 優(yōu)點: “有容乃大”洽蛀、全面;
- 缺點: struct內存空間的分配是粗放的疟赊,不管用不用郊供,全分配。
聯(lián)合體(union)
中是各變量是“互斥”的
- 優(yōu)點: 內存使用更為精細靈活近哟,也節(jié)省了內存空間;
- 缺點: 就是不夠“包容”驮审。
蘋果也針對于這種現(xiàn)象做了一些優(yōu)化,采用聯(lián)合體吉执、位域
的方式來節(jié)省內存以存儲更多內容疯淫。
@interface FXCar(){
// 聯(lián)合體
union {
char bits;
// 位域
struct { // 0000 1111
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
} _direction;
}
@end
三、isa結構信息
在alloc流程分析中我們針對alloc進行源碼調試過程中戳玫,我們會走到obj->initInstanceIsa
這個方法熙掺,我們按照obj->initInstanceIsa
->initIsa
-> isa_t
流程可以分別看到下面幾個方法
obj->initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
obj->initInstanceIsa
方法中的initIsa
方法
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;
}
}
從initIsa
方法中,我們可以看到isa = isa_t((uintptr_t)cls);
咕宿,isa的數(shù)據結構其實為 isa_t
币绩,然后我們再進入isa_t
看一下蜡秽。
isa_t
源碼
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
};
通過上述源碼發(fā)現(xiàn)isa_t
是一個union(共用體/聯(lián)合體)
其中 ISA_BITFIELD
宏定義在不同架構下表示如下 :
# if __arm64__
# 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
# elif __x86_64__
# 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
首先看到isa_t
是一個聯(lián)合體的數(shù)據結構 , 聯(lián)合體意味著公用內存 , 也就是說isa
其實總共還是占用 8 個字節(jié)內存 , 共 64 個二進制位 。
而上述不同架構的宏定義中定義的位域就是 64 個二進制位中 , 每個位置存儲的是什么內容类浪。
- 由于聯(lián)合體的特性 ,
cls
,bits
以及struct
都是 8 字節(jié)內存 , 也就是說他們在內存中是完全重疊的载城。- 實際上在
runtime
中,任何對struct
的操作和獲取某些值费就,如extra_rc
诉瓦,實際上都是通過對bits
做位運算實現(xiàn)的。bits
和struct
的關系可以看做 :bits
向外提供了操作struct
的接口力细,而struct
本身則說明了bits
中各個二進制位的定義睬澡。
參照arm64
架構下 ,ISA_BITFIELD
我們來看看每個字段都存儲了什么內容 , 以便更深刻的理解對象的本質。
成員 | 位 | 含義 |
---|---|---|
nonpointer | 1bit | 表示是否對 isa 指針開啟指針優(yōu)化眠蚂。 0:純 isa 指針煞聪;1:不止是類對象地址。isa 中包含了類信息逝慧、對象的引用計數(shù)等 |
has_assoc | 1bit | 標志位: 表明對象是否有關聯(lián)對象昔脯。0:沒有;1:存在笛臣。沒有關聯(lián)對象的對象釋放的更快 |
has_cxx_dtor | 1bit | 標志位: 表明對象是否有C++或ARC析構函數(shù)云稚。沒有析構函數(shù)的對象釋放的更快 |
shiftcls | 33bit | 存儲類指針的值。開啟指針優(yōu)化的情況下沈堡,在 arm64 架構中有 33 位用來存儲類指針静陈。 |
magic | 6bit | 用于調試器判斷當前對象是真的對象還是沒有初始化的空間 , 固定為 0x1a |
weakly_referenced | 1bit | 標志位:用于表示該對象是否被別ARC對象弱引用或者引用過。沒有被弱引用的對象釋放的更快 |
deallocating | 1bit | 標志位: 用于表示該對象是否正在被釋放 |
has_sidetable_rc | 1bit | 標志位: 用于標識是否當前的引用計數(shù)過大 ( 大于 10 ) 诞丽,無法在 isa 中存儲鲸拥,則需要借用sidetable來存儲,標志是否有外掛的散列表 |
extra_rc | 19bit | 實際上是對象的引用計數(shù)減 1 . 比如僧免,一個 object 對象的引用計數(shù)為7刑赶,則此時 extra_rc 的值為 6 |
四、isa關聯(lián)類
在上面的initIsa方法
中懂衩,isa初始化
方法中會有如下賦值角撞,我們也可以斷點調試 newisa
的值
isa初始化方法
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;
newisa
的值
(lldb) p newisa
(isa_t) $2 = {
cls = 0x001d800000000001
··
= {
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
}
}
isa初始化
方法當中的宏 ISA_MAGIC_VALUE
(ULL表示unsign long long類型
)
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
我們可以用計算器看一下isa
當中的 cls = 0x001d800000000001
信息,
我們可以看到從47位開始有一個111011勃痴,然后我們再打印一下
isa
當中的 magic = 59
信息谒所,使用計算器打印一下59的二進制信息,我們會發(fā)現(xiàn)也是11101沛申。然后我們再看一下cls = 0x001d800000000001
信息的十進制信息劣领,發(fā)現(xiàn)就等于 bits = 8303511812964353
,這也就說明了cls
,bits
以及struct
都是 8 字節(jié)內存 , 也就是說他們在內存中是完全重疊的铁材。
然后我們把斷點調試到下面這行代碼執(zhí)行完畢
newisa.shiftcls = (uintptr_t)cls >> 3;
我們 (uintptr_t)cls
右移3位尖淘,可以打印如下信息
(lldb) p (uintptr_t)cls
(uintptr_t) $4 = 4294976104
(lldb) p $4 >> 3
(uintptr_t) $5 = 536872013
(lldb) p newisa
(isa_t) $6 = {
cls = FXPerson
bits = 8303516107940461
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 1
shiftcls = 536872013
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
從上面我們可以看到奕锌,shiftcls
存儲類指針的值,并且將類 FXPerson
和 shiftcls = 536872013
關聯(lián)起來了村生。
五惊暴、isa通過位運算驗證關聯(lián)類
我們先將斷點調試到 obj->initInstanceIsa
這個方法之后,然后打印 obj
信息趁桃,通過位運算驗證關聯(lián) FXPerson
信息
位運算方法解析:isa結構體信息如下圖所示:
- 先將
isa指針
右移3位
將isa指針
的右邊3位
信息抹掉辽话,左邊空出3位
使用0
來填充。 - 將
isa指針
左移20位
卫病,將步驟1
當中新增的3位
+ isa指針的左邊17信息
抹掉油啤。 - 最后將
isa指針
右移17
位即可恢復44位shiftcls
的位置信息
(lldb) x/4gx obj
0x103225090: 0x001d800100002275 0x0000000000000000
0x1032250a0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x001d800100002275 >> 3
(long) $2 = 0x0003b0002000044e
(lldb) p/x 0x0003b0002000044e << 20
(long) $3 = 0x0002000044e00000
(lldb) p/x 0x0002000044e00000 >> 17
(long) $4 = 0x0000000100002270
(lldb) p/x cls
(Class) $5 = 0x0000000100002270 FXPerson
到此isa位運算關聯(lián)類信息驗證完畢!
六蟀苛、通過 isa & ISA_MSAK可以查看isa指向類信息
arm64
和 x86_64
中掩碼 ISA_MASK
定義
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
...
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
...
其實一步一步的位運算不免顯得有些繁瑣益咬,蘋果為大家提供了和alloc流程分析中內存對齊一樣的算法,提供一個掩碼執(zhí)行 與操作
(&
)帜平,即可得出 isa
指向的類信息幽告。
(lldb) po 0x001d800100002275 & 0x00007ffffffffff8ULL
FXPerson