歡迎閱讀iOS探索系列(按序閱讀食用效果更加)html
在介紹isa以前,先介紹一個位域和結構體的知識點bash
有些信息在存儲時,並不須要佔用一個完整的字節,而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有0和1兩種狀態,用1位二進位便可。爲了節省存儲空間並使處理簡便,C語言
提供了一種數據結構,稱爲位域
或位段
數據結構
所謂位域
就是把一個字節中的二進位劃分爲幾個不一樣的區域,並說明每一個區域的位數。每一個域有一個域名,容許在程序中按域名進行操做——這樣就能夠把幾個不一樣的對象用一個字節的二進制位域來表示架構
位域的使用與結構體相仿,它自己也是結構體的一種ide
// 結構體
struct FXStruct {
// (類型說明符 元素);
char a;
int b;
} FXStr;
// 位域
struct FXBitArea {
// (類型說明符 位域名: 位域長度);
char a: 1;
int b: 3;
} FXBit;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Struct:%lu——BitArea:%lu", sizeof(FXStr), sizeof(FXBit));
}
return 0;
}
複製代碼
輸出Struct:8——BitArea:4
函數
對位域
有興趣的能夠看下struct中位域的定義post
當多個數據須要共享內存或者多個數據每次只取其一時,能夠利用聯合體(union)
性能
結構體每一個成員依次存儲,聯合體中全部成員的偏移地址都是0,也就是全部成員是疊在一塊兒的,因此在聯合體中在某一時刻,只有一個成員有效——結構體內存大小取決於全部元素,聯合體取決於最大那個
優化
在以前的iOS探索 alloc流程
中輕描淡寫的提了一句obj->initInstanceIsa(cls, hasCxxDtor)
——只知道內部調用initIsa(cls, true, hasCxxDtor)
初始化isa,並無對isa進行細說ui
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);
#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;
}
}
複製代碼
①建立對象跟着斷點不難發現nonpointer
爲true
②if-else跳轉了else流程——SUPPORT_INDEXED_ISA
表示isa_t
中存放的 Class
信息是Class 的地址
,仍是一個索引
(根據該索引可在類信息表中查找該類結構地址)
③isa_t newisa(0)
至關於初始化isa這個東西,new.
至關於給isa賦值屬性
1.SUPPORT_INDEXED_ISA適用於WatchOS 2.isa做爲聯合體具備互斥性,而cls、bits是isa的元素,因此當!nonpointer=true時對cls進行賦值操做,爲false是對bits進行賦值操做(反正都是一家人,共用一塊內存地址)
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
是個聯合體,擁有兩個初始化方法
②isa
內部有個Class cls
——Class
和isa
有綁定關係——isa
指向類的結構
③isa採用聯合體+位域
的形式來優化內存(ISA_BITFIELD
是個位域宏定義)
先初始化bits
決定聯合體的長度,再對聯合體內的位域ISA_BITFIELD
進行賦值
聯合體全部屬性共用內存,內存長度等於其最長成員的長度,使代碼存儲數據高效率的同時,有較強的可讀性;而位域能夠容納更多類型
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# 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
// SUPPORT_PACKED_ISA
#endif
複製代碼
不一樣架構下isa所佔內存均爲8字節
——64位
,但內部分佈有所不一樣,arm64架構
isa內部成員分佈以下圖
nonpointer
:表示是否對isa指針開啓指針優化——0:純isa指針;1:不止是類對象地址,isa 中包含了類信息、對象的引用計數等
has_assoc
:關聯對象標誌位,0沒有,1存在
has_cxx_dtor
:該對象是否有 C++ 或者 Objc 的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象
shiftcls
:存儲類指針的值,在開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針
magic
:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間
weakly_referenced
:對象是否被指向或者曾經指向一個 ARC 的弱變量, 沒有弱引用的對象能夠更快釋放
deallocating
:標誌對象是否正在釋放內存
has_sidetable_rc
:當對象引用技術大於 10 時,則須要借用該變量存儲進位
extra_rc
:當表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc 爲 9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rc
除
shiftcls
以外瞭解便可
@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
FXPerson *p = [[FXPerson alloc] init];
NSLog(@"%@",p);
}
return 0;
}
複製代碼
從父類NSObject
結構中能夠有個isa屬性
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼
可是內存中屬性的位置是會由於優化發生改變的,下面就來證明下內存中第一位必定是isa
①打印第一位內存
②二進制打印第一位內存的內存值
③由於模擬器是x86架構
的,由isa
位域結構可知,shiftcls
前面有3位——右移3位——抹去isa前3位
④shiftcls
後面有17位——左移17位——抹去isa後17位
⑤由於末尾的0都是咱們添加的——右移17位——獲得shiftcls
⑥根據newisa.shiftcls = (uintptr_t)cls >> 3;
——shiftcls
等於class
地址右移3位
比對兩組shiftcls
二進制,發現它們二進制同樣(前面不同是由於沒抹掉)
若是你聽得雲裏霧裏的話,請看第二種方法
①在<objc/runtime.h>
下使用object_getClass
方法
#import <objc/runtime.h>
@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
FXPerson *p = [[FXPerson alloc] init];
object_getClass(p);
}
return 0;
}
複製代碼
②跟進object_getClass
方法
/*********************************************************************** * object_getClass. * Locking: None. If you add locking, tell gdb (rdar://7516456). **********************************************************************/
Class object_getClass(id obj) {
if (obj) return obj->getIsa();
else return Nil;
}
複製代碼
③跟進getIsa()
#if SUPPORT_TAGGED_POINTERS
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
複製代碼
④通常isTaggedPointer
都爲false,跟進ISA()
#if SUPPORT_NONPOINTER_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
}
複製代碼
⑤已知SUPPORT_INDEXED_ISA
適用於WatchOS,那麼走return (Class)(isa.bits & ISA_MASK);
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
複製代碼
⑥檢驗
打印出isa & mask
的值,與class
相比較(mask
取x86架構)
從上述兩種方法都能得出
實例對象首地址必定 是isa
@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
Class class1 = [FXPerson class];
Class class2 = [FXPerson alloc].class;
Class class3 = object_getClass([FXPerson alloc]);
Class class4 = [FXPerson alloc].class;
NSLog(@"\n%p\n%p\n%p\n%p",class1,class2,class3,class4);
}
return 0;
}
複製代碼
0x1000024a0
0x1000024a0
0x1000024a0
0x1000024a0
複製代碼
輸出證實類在內存中只會存在一個,而實例對象能夠存在多個(自行證實)
類
其實和實例對象
同樣,都是由上級實例化出來的——類的上級叫作元類
咱們先用p/x
打印類的內存地址,再用x/4gx
打印內存結構取到對應的isa,再用mask
進行偏移獲得isa指向的上級
(等同於object_getClass
)依次循環
FXPerson類
取得isa
②由FXPerson類
進行偏移獲得FXPerson元類
指針,打印FXPerson元類
取得isa
③由FXPerson元類
進行偏移獲得NSObject根元類
指針,打印NSObject根元類
取得isa
④由NSObject根元類
進行偏移獲得NSObject根元類
自己指針
⑤打印NSObject根類
取得isa
⑥由NSObject根類
進行偏移獲得NSObject根元類
指針
結論:
①實例對象-> 類對象 -> 元類 -> 根元類 -> 根元類(自己)
②NSObject(根類) -> 根元類 -> 根元類(自己)
③指向根元類的isa都是同樣的
int main(int argc, const char * argv[]) {
@autoreleasepool {
// NSObject實例對象
NSObject *object1 = [NSObject alloc];
// NSObject類
Class class = object_getClass(object1);
// NSObject元類
Class metaClass = object_getClass(class);
// NSObject根元類
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元類
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 實例對象\n%p 類\n%p 元類\n%p 根元類\n%p 根根元類",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
return 0;
}
複製代碼
0x100660ba0 實例對象
0x7fffacd3d140 類
0x7fffacd3d0f0 元類
0x7fffacd3d0f0 根元類
0x7fffacd3d0f0 根根元類
複製代碼
由於是NSObject
(根類)它的元類就是根元類
——輸出可得根元類指向本身
①運行時僞證法
main
以前
FXPerson類
和
FXPerson元類
已經存在在內存中,不過此時程序已經在運行了,並無什麼說服力
②查看MachO文件法
FXPerson
已經存在在MachO文件中
結論:
對象是程序猿根據類實例化的
類是代碼編寫的,內存中只有一份,是系統建立的
元類是系統編譯時,系統編譯器建立的,便於方法的編譯
複製代碼
實例對象-> 類對象 -> 元類 -> 根元類 -> 根元類(自己)
繼承關係(實線):NSObject父類爲nil,根元類的父類爲NSObject
None[-O0]
: 不優化。在這種設置下, 編譯器的目標是下降編譯消耗,保證調試時輸出指望的結果。程序的語句之間是獨立的:若是在程序的停在某一行的斷點出,咱們能夠給任何變量賦新值抑或是將程序計數器指向方法中的任何一個語句,而且能獲得一個和源碼徹底一致的運行結果。Fast[-O1]
: 大函數所需的編譯時間和內存消耗都會稍微增長。在這種設置下,編譯器會嘗試減少代碼文件的大小,減小執行時間,但並不執行須要大量編譯時間的優化。在蘋果的編譯器中,在優化過程當中,嚴格別名,塊重排和塊間的調度都會被默認禁止掉。Faster[-O2]
: 編譯器執行全部不涉及時間空間交換的全部的支持的優化選項。在這種設置下,編譯器不會進行循環展開、函數內聯或寄存器重命名。和'Fast[-O1]'項相比,此設置會增長編譯時間和生成代碼的性能。Fastest[-O3]
: 在開啓'Fast[-O1]'項支持的全部優化項的同時,開啓函數內聯和寄存器重命名選項。這個設置有可能會致使二進制文件變大。Fastest, Smallest[-Os]
: 優化大小。這個設置開啓了'Fast[-O1]'項中的全部不增長代碼大小的優化選項,並會進一步的執行能夠減少代碼大小的優化。Fastest, Aggressive Optimizations[-Ofast]
: 這個設置開啓了'Fastest[-O3]'中的全部優化選項,同時也開啓了可能會打破嚴格編譯標準的積極優化,但並不會影響運行良好的代碼。Debug默認不優化,Relese默認Fastest, Smallest[-Os]