先看下面的代碼:markdown
struct SSLCar {
BOOL front;
BOOL back;
BOOL left;
BOOL right;
}sslCar;
NSLog(@"sslCar:%lu",sizeof(sslCar));
打印結果:sslCar:4
複製代碼
咱們看到一個SSLCar
結構體是4
個字節,也就是32
位(0000 0000 0000 0000 0000 0000 0000 0000
),而SSLCar
結構體中4
個BOOL
值只須要4
位(1111
)就能夠存儲,這形成了很是大的空間浪費。架構
經過位域指定每一個成員變量佔1
位:ide
struct SSLCar2 {
BOOL front: 1;
BOOL back : 1;
BOOL left : 1;
BOOL right: 1;
}sslCar2;
NSLog(@"sslCar2:%lu",sizeof(sslCar2));
打印結果:sslCar2:1
複製代碼
此時內存空間已經獲得了不錯的優化。函數
建立聯合體:post
union SSLPerson {
char *name;
int age;
};
複製代碼
爲聯合體賦值,並打印:優化
經過打印結果,發現聯合體只有後賦值的變量纔有值。ui
結構體(struct
)中全部變量是「共存」的——優勢
是「有容乃大」,全面;缺點
是struct
內存空間的分配是粗放的,無論用不用,全分配。this
聯合體(union
)中是各變量是「互斥」的——缺點
就是不夠「包容」; 但優勢
是內存使用更爲精細靈活,也節省了內存空間。atom
在main.m
中添加以下代碼:spa
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *sslName;
@end
@implementation SSLPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
複製代碼
經過clang
命令編譯main.m
(更多clang
信息點這裏):
clang -rewrite-objc main.m -o main.cpp
複製代碼
獲得main.cpp
文件,在main.cpp
中咱們能夠看到有關對象的信息:
typedef struct objc_class *Class; // 7658行
struct objc_object { // 7661 行
Class _Nonnull isa __attribute__((deprecated));
};
typedef struct objc_object *id; // 7666行
typedef struct objc_object SSLPerson; // 111788行
struct SSLPerson_IMPL { // 111793行
struct NSObject_IMPL NSObject_IVARS;
NSString *_sslName;
};
複製代碼
由此咱們能夠得出,對象是一個objc_object
類型的結構體,內部有一個Class
類型的isa
指針。
打開源碼,查看objc_object
:
struct objc_object {
private:
isa_t isa;
...
}
複製代碼
咱們在 OC 對象原理探索(一) 中,提到過obj->initIsa(cls)
,它的做用是將指針和類進行關聯,下面咱們用源碼進行詳細的分析。
initIsa
函數:
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
ASSERT(!isTaggedPointer());
isa_t newisa(0); // isa初始化
if (!nonpointer) {
newisa.setClass(cls, this);//若是是純指針 isa被直接cls賦值
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
複製代碼
點擊查看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_BITFIELD
:
# 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)
複製代碼
看一下isa
的內存分佈圖:
nonpointer
:表示是否對isa
指針開啓指針優化。0
:純isa
指針,1
:不止是類對象地址,isa
中包含了類信息、對象的引用計數等。has_assoc
:關聯對象標誌位,0
沒有,1
存在。has_cxx_dtor
:該對象是否有C++
或者Objc
的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象。shiftcls
:存儲類指針的值。開啓指針優化的狀況下,在arm64
架構中有33
位用來存儲類指針。magic
:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間。weakly_referenced
:標誌對象是否被指向或者曾經指向一個ARC
的弱變量,沒有弱引用的對象能夠更快釋放。deallocating
:標誌對象是否正在釋放內存。extra_rc
:表示該對象的引用計數值,若是不夠存儲,has_sidetable_rc
的值就會變爲 1;has_sidetable_rc
:若是爲1
,表明引用計數過大沒法存儲在isa
中,那麼超出的引用計數會存儲在SideTable
的RefCountMap
中。因此,若是isa
是nonpointer
,則對象的引用計數存儲在它的isa_t
的extra_rc
中以及SideTable
的RefCountMap
中。總結:
isa
分爲nonpointer
類型和非nonpointer
類型。非nonpointer
類型是一個純指針,nonpointer
類型還包含了引用計數、關聯對象等信息。isa
採用聯合體
+位域
的方式存儲信息。由於在iOS
中萬物皆對象,對象中都用到了isa
,這樣能夠大大的節省內存空間。先建立一個SSLperson
類,初始化[SSLPerson alloc]
,在initIsa
函數中斷點調試:
如上圖,建立了一個聯合體newisa
,裏邊的變量目前都是0
,沒有賦值。
繼續向下運行:
如上圖,newisa.bits = ISA_MAGIC_VALUE
,ISA_MAGIC_VALUE
是一個宏 =0x001d800000000001
。被賦值的變量有bits
=8303511812964353
,cls
=0x001d800000000001
,nonpointer
=1
,magic
=59
。轉化成二進制表示:
0x001d800000000001
=8303511812964353
,它們的二進制表示相同。nonpointer
的二進制表示是1
,magic
的二進制表示是111011
,它們包含在bits
的二進制表示中。斷點進入setClass
:
isa
和SSLPerson
類進行了關聯。nonpointer
,因此並不會給cls
賦值。SSLPerson
的類地址>>3
進行10
進制轉換,而後賦值給shiftcls
。
3位
呢,由於shiftcls
在isa
中是從第四位開始存儲的,這樣isa & ISA_MASK
才能獲得準確的類地址
。繼續向下運行:
shiftcls
,extra_rc
也被成功賦值。cls
明明沒有被賦值,爲何倒是SSLPerson
呢,並且聯合體
也應該會把cls
覆蓋掉纔對,因此我後期有作了一個調試:
cls
的值,它確實是整個isa
的值,至於爲何顯示SSLPerson
,應該是系統作了處理。到此,isa
和類
關聯的探索基本完成,之後若有更深刻理解會持續更新。