一艾帐、涉及知識點
1.共用體(聯(lián)合體)
定義
在進行某些算法的C語言編程的時候誓沸,需要使幾種不同類型的變量存放到同一段內存單元中否副,也就是使用覆蓋技術,幾個變量互相覆蓋愕乎,以達到節(jié)省空間的目的阵苇,這種幾個不同的變量共同占用一段內存的結構叫共用體,又稱聯(lián)合體妆毕。
特點:
共用體和結構體有下列區(qū)別:
a.結構體的成員之間是共存的:各個成員占用不同的內存慎玖,它們互相之間沒有影響。
b.聯(lián)合體的成員之間是互斥的:所有成員共用同一段內存笛粘,修改一個成員的值趁怔,會影響其余所有成員。
c.結構體占用的內存:大于等于所有成員占用內存的總和(需要內存對齊)
d.聯(lián)合體占用的內存:等于最大的成員占用的內存薪前,同一時刻只能保存一個成員的值
聲明
#pragma mark 共用體
union WJUnion {
int a;
float b;
char c;
};
2.位域
定義
C語言允許在一個結構體中以位為單位來指定其成員所占內存長度润努,這種以位為單位的成員稱為“位段”或稱位域( bit field) 。利用位段能夠用較少的位數存儲數據示括。
特點:
a.位段成員的類型必須指定為unsigned或int類型铺浇;
b.若某一位段要從另一個字開始存放,用:0長度為0的空位段垛膝,作用就是使下一個位段從下一個存儲單位(視不同編譯系統(tǒng)而異)開始存放鳍侣;
c.一個位段必須存儲在同一存儲單元中丁稀,不能跨兩個單元;
d.可以定義無名字段例如":2"倚聚;
e.位段的長度不能大于存儲單元的長度线衫,也不能定義位段數組;
f.位段可以用整形格式符輸出惑折;
g.位段可以在數值表達式中引用授账,它會被系統(tǒng)自動地轉換成整形數;
聲明
#pragma mark 結構體
struct WJCarStruct {
BOOL front;
BOOL back;
BOOL left;
BOOL right;
};
#pragma mark 位域
struct WJCarStructBit {
BOOL front : 1;
BOOL back : 1;
BOOL left : 1;
BOOL right : 1;
};
分析:
結構體WJCarStruct占4個字節(jié),因為每個BOOL各占1個字節(jié)惨驶;
位域WJCarStructBit占1個字節(jié)白热,對比WJCarStruct結構體節(jié)省3個字節(jié)。
二粗卜、OC對象底層分析輔助工具(clang編譯源碼成底層代碼)
1.clang定義
clang簡而言之是一個C語言屋确、C++、Objective-C語言的輕量級編譯器休建,源代碼發(fā)布于BSD協(xié)議下乍恐。Clang將支持其普通lambda表達式、返回類型的簡化處理以及更好的處理constexpr關鍵字
2.在終端使用clang相關命令編譯WJPerson.m生成WJPerson.cpp底層文件
2.1 使用clang命令
# clang -rewrite-objc WJPerson.m -o WJPerson.cpp
2.2 使用Xcode中的xcrun命令
# xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc WJPerson.m -o WJPerson.cpp
3.打開WJPerson.cpp并找到WJPerson類
typedef struct objc_object WJPerson;
typedef struct {} _objc_exc_WJPerson;
extern "C" unsigned long OBJC_IVAR_$_WJPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_WJPerson$_nickName;
struct WJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //就是isa指針
NSString * _Nonnull _name; //屬性name對應的成員變量
NSString * _Nonnull _nickName; //屬性nickName對應的成員變量
};
// @property (nonatomic, copy)NSString *name;
// @property (nonatomic, copy)NSString *nickName;
/* @end */
從編譯之后的文件找到WJPerson_IMPL测砂,可以看出WJPerson類實際上是一個結構體茵烈;由此可見,在OC中類的本質就是結構體(struct)砌些。
3.1 來看結構體中的成員變量_name和_nickName呜投,其實就是WJPerson頭文件中聲明的兩個屬性name和nickName所對應的。
@interface WJPerson : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *nickName;
@end
3.2 再來看結構體NSObject_IMPL是什么呢存璃?
在WJPerson.cpp中搜索NSObject_IMPL仑荐,發(fā)現(xiàn)其實就是isa指針,
struct NSObject_IMPL {
Class isa;
};
在OC中基本上所有的對象都是繼承NSObject纵东,但是真正的底層實現(xiàn)是objc_object的結構體類型
3.3 對象的本質拓展
在WJPerson.cpp文件中全局搜索*Class時粘招,發(fā)現(xiàn)有這樣幾行代碼如下:
typedef struct objc_class *Class;
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
源碼分析:
常用的id原來是也是一個objc_object結構體指針別稱,這就解釋了id修飾變量和作為返回值的時候為什么不加*了偎球,此外還有Class洒扎、SEL等也是結構體指針。
三衰絮、nonPointerIsa分析
對象alloc流程核心三步曲
a.instanceSize袍冷,計算開辟內存需要的大小。
b.calloc猫牡,向系統(tǒng)申請開辟內存胡诗,返回地址指針。
c.initInstanceIsa,初始化指針和類關聯(lián)起來煌恢。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
進入initIsa方法流程
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
......
}
isa = newisa;
}
進入isa_t,發(fā)現(xiàn)isa_t就是一個共用體瑰抵,源碼如下:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
ISA_BITFIELD分析
在表現(xiàn)一個類的地址時缩歪,會出現(xiàn)一個詞:nonPointerIsa。就比如一個類谍憔,也就可以作為一個指針,類上面是可以有很多內容是能夠被存儲的主籍。類的指針是8字節(jié),8字節(jié) * 8 bit = 64 bit(64位)。那么如果只是用來存儲一個指針绑莺,就會造成大大的浪費蔓姚,因為每個類都有一個isa指針。蘋果就對這個isa做了優(yōu)化幸海,就把和類息息相關的一些內容存在里面祟身,比如:是否正在釋放、引用計數物独、weak袜硫、關聯(lián)對象、析構函數等等(所以挡篓,OC在底層婉陷,就是C++,像OC的釋放官研,并不是真正的釋放秽澳,而是其下層的C++釋放,才是真正的釋放),這些都和類先關戏羽,所以担神,可以把這些內容存儲到那64位里面去。那么就出現(xiàn)了nonPointerIsa始花。nonPointerIsa也不是一個簡單的地址妄讯。我們可以通過查看isa_t的位域,來了解里面存的是什么衙荐。
在arm64中:
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t shiftcls_and_sig : 52; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
字段說明如下:
nonpointer:
表示是否對isa指針開啟指針優(yōu)化 0:純isa指針捞挥,1:不?是類對象地址,isa中包含了類信息、對象的引?計數等忧吟;
has_assoc:
關聯(lián)對象標志位砌函,0沒有,1存在;
has_cxx_dtor:
該對象是否有C++或者Objc的析構器,如果有析構函數,則需要做析構邏輯,如果沒有,則可以更快的釋放對象讹俊;
shiftcls:
存儲類指針的值垦沉,開啟指針優(yōu)化的情況下,在arm64架構中有33位?來存儲類指針仍劈;
magic:
?于調試器判斷當前對象是真的對象還是沒有初始化的空間厕倍;
weakly_referenced:志對象是否被指向或者曾經指向?個ARC的弱變量,沒有弱引?的對象可以更快釋放贩疙;
deallocating:
標志對象是否正在釋放內存讹弯;
has_sidetable_rc:
當對象引?技術?于10時,則需要借?該變量存儲進位这溅;
extra_rc:
當表示該對象的引?計數值组民,實際上是引?計數值減1,例如悲靴,如果對象的引?計數為10臭胜,那么extra_rc為9。如果引?計數?于10癞尚,則需要使?到下?的has_sidetable_rc耸三。
以arm64為例,堆中浇揩,8字節(jié)對齊排列仪壮,8字節(jié) * 8 bit = 64 bit(64位)。
那么胳徽,nonpointer占[1]號位置睛驳,has_assoc占[2]號位置,has_cxx_dtor占[3]號位置膜廊,shiftcls占[4 ~ 36]號位置乏沸,magic占[37~42]號位置,weakly_referenced占[43]號位置爪瓜,deallocating占[44]號位置蹬跃,has_sidetable_rc占[45]號位置,extra_rc占[46 ~ 64]號位置铆铆。
總結:
1.OC中類的本質就是結構體蝶缀。
2.ISA是通過共用體(聯(lián)合體)互斥的特性,確定ISA是純的ISA還是NONPOINTER_ISA,并通過位域來實現(xiàn)節(jié)約空間薄货。