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
文件markdown
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指針
。架構
@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字節*8位 = 32位 浪費
- (void)setFront:(BOOL)isFront; // 存儲 : 1字節 = 8位 0000 1111 char + 位域 bit 結構體
- (BOOL)isFront;
- (void)setBack:(BOOL)isBack;
- (BOOL)isBack;
@end
複製代碼
假設咱們一個對象(FXCar)
裏面有4個屬性(front、back、left、right)
,每一個BOOL屬性佔1個字節,4個屬性就佔4個字節(32位數據)。其實只須要一個字節後面4位的內容就能夠存儲這4個屬性。ide
結構體(struct)
中全部變量是「共存」的函數
聯合體(union)
中是各變量是「互斥」的oop
蘋果也針對於這種現象作了一些優化,採用聯合體、位域
的方式來節省內存以存儲更多內容。優化
@interface FXCar(){
// 聯合體
union {
char bits;
// 位域
struct { // 0000 1111
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
} _direction;
}
@end
複製代碼
在alloc流程分析中咱們針對alloc進行源碼調試過程當中,咱們會走到obj->initInstanceIsa
這個方法,咱們按照obj->initInstanceIsa
->initIsa
-> isa_t
流程能夠分別看到下面幾個方法ui
obj->initInstanceIsa
atom
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的數據結構其實爲 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
};
複製代碼
經過上述源碼發現isa_t
是一個union(共用體/聯合體)
其中 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
是一個聯合體的數據結構 , 聯合體意味着公用內存 , 也就是說isa
其實總共仍是佔用 8 個字節內存 , 共 64 個二進制位 。
而上述不一樣架構的宏定義中定義的位域就是 64 個二進制位中 , 每一個位置存儲的是什麼內容。
- 因爲聯合體的特性 ,
cls
,bits
以及struct
都是 8 字節內存 , 也就是說他們在內存中是徹底重疊的。- 實際上在
runtime
中,任何對struct
的操做和獲取某些值,如extra_rc
,實際上都是經過對bits
作位運算實現的。bits
和struct
的關係能夠看作 :bits
向外提供了操做struct
的接口,而struct
自己則說明了bits
中各個二進制位的定義。
參照arm64
架構下 ,ISA_BITFIELD
咱們來看看每一個字段都存儲了什麼內容 , 以便更深入的理解對象的本質。
成員 | 位 | 含義 |
---|---|---|
nonpointer | 1bit | 表示是否對 isa 指針開啓指針優化。 0:純 isa 指針;1:不止是類對象地址。isa 中包含了類信息、對象的引用計數等 |
has_assoc | 1bit | 標誌位: 代表對象是否有關聯對象。0:沒有;1:存在。沒有關聯對象的對象釋放的更快 |
has_cxx_dtor | 1bit | 標誌位: 代表對象是否有C++或ARC析構函數。沒有析構函數的對象釋放的更快 |
shiftcls | 33bit | 存儲類指針的值。開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針。 |
magic | 6bit | 用於調試器判斷當前對象是真的對象仍是沒有初始化的空間 , 固定爲 0x1a |
weakly_referenced | 1bit | 標誌位:用於表示該對象是否被別ARC對象弱引用或者引用過。沒有被弱引用的對象釋放的更快 |
deallocating | 1bit | 標誌位: 用於表示該對象是否正在被釋放 |
has_sidetable_rc | 1bit | 標誌位: 用於標識是否當前的引用計數過大 ( 大於 10 ) ,沒法在 isa 中存儲,則須要借用sidetable來存儲,標誌是否有外掛的散列表 |
extra_rc | 19bit | 其實是對象的引用計數減 1 . 好比,一個 object 對象的引用計數爲7,則此時 extra_rc 的值爲 6 |
### 4、isa關聯類 | ||
在上面的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的二進制信息,咱們會發現也是11101。而後咱們再看一下cls = 0x001d800000000001
信息的十進制信息,發現就等於 bits = 8303511812964353
,這也就說明了cls
,bits
以及struct
都是 8 字節內存 , 也就是說他們在內存中是徹底重疊的。
而後咱們把斷點調試到下面這行代碼執行完畢
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
關聯起來了。
咱們先將斷點調試到 obj->initInstanceIsa
這個方法以後,而後打印 obj
信息,經過位運算驗證關聯 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位運算關聯類信息驗證完畢!
arm64
和 x86_64
中掩碼 ISA_MASK
定義
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
...
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
...
複製代碼
其實一步一步的位運算難免顯得有些繁瑣,蘋果爲你們提供了和alloc流程分析中內存對齊同樣的算法,提供一個掩碼執行 與操做
(&
),便可得出 isa
指向的類信息。
(lldb) po 0x001d800100002275 & 0x00007ffffffffff8ULL
FXPerson
複製代碼