今天我們來對(duì)OC對(duì)象的原理進(jìn)行最后一篇文章的分析锰霜,在這里你講了解到一下內(nèi)容:
1乓搬、對(duì)象的底層本質(zhì)
2、聯(lián)合體位域
3车份、
isa
和Class
的關(guān)系4、isa 的Class 的賦值反過程(通過位運(yùn)算得到
Class
地址)參考文章:C 位域
1牡彻、對(duì)象的底層本質(zhì)
對(duì)象在底層的本質(zhì)扫沼,實(shí)際上是一個(gè)結(jié)構(gòu)體,這一點(diǎn)我們可以用C++輔助代碼來看一下庄吼。還記不記得我們?cè)谔剿?code>Block底層原理的時(shí)候缎除,用到的指令clang -rewrite-objc XXX.m
。同樣的总寻,這里我們也將Person
類器罐,轉(zhuǎn)換成C++來看一下其底層到底是什么。
/******** Person.h ********/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
/******** Person.m ********/
#import "Person.h"
@implementation Person
@end
執(zhí)行指令clang -rewrite-objc Person.m
我們得到Person.cpp
(注:在Person.cpp
文件中渐行,查看Person轰坊,通常從最下面開始查找比較方便)。
我們?cè)?code>Person.cpp文件中可以看到祟印,Person
這個(gè)類在底層就是一個(gè)結(jié)構(gòu)體衰倦,那么Person
類所創(chuàng)建的對(duì)象,在底層的數(shù)據(jù)結(jié)構(gòu)就是一個(gè)結(jié)構(gòu)體旁理。
- Person_IMPL
Person_IMPL
中struct NSObject_IMPL NSObject_IVARS;
相當(dāng)于結(jié)構(gòu)體的繼承樊零,通過字面意思我們也可以知道,繼承的是成員變量(Ivar)孽文,那么我們跟進(jìn)去看一下:
可以看到是一個(gè)
isa
指針驻襟,這也就意味著,在OC中芋哭,每一個(gè)對(duì)象都會(huì)有一個(gè)isa
指針沉衣,因?yàn)檫@是系統(tǒng)幫我們自動(dòng)完成的。
1.1 Class
這里的Class
又是什么呢减牺?其實(shí)Class
就是一個(gè)結(jié)構(gòu)體指針:
1.2 id
大家注意上圖豌习,我們還看到
typedef struct objc_object *id;
NSObject
在底層中是objc_object
;看到這里的id
拔疚,我突然想明白了一點(diǎn)肥隆,為什么我們?cè)趯懘a的時(shí)候,用id
去修飾對(duì)象不會(huì)報(bào)錯(cuò)稚失,比如id person
等等栋艳。就是因?yàn)?code>id本身就是一個(gè)指針。
1.3 set & get
Person
中name
的set & get
方法:
大家會(huì)發(fā)現(xiàn)句各,為什么在底層中
set & get
方法會(huì)有參數(shù)呢吸占?這就是我們經(jīng)常說的隱藏參數(shù)晴叨。
這里跟大家簡(jiǎn)單講一下,大家注意看
OBJC_IVAR_$_Person$_name
是什么矾屯?它其實(shí)是一個(gè)unsigned long
:extern "C" unsigned long OBJC_IVAR_$_Person$_name;
這里的取值與賦值兼蕊,都是對(duì)內(nèi)存地址的操作,拿到Person
對(duì)象的首地址件蚕,偏移到name
所在的位置孙技,再進(jìn)行取值 或者 賦值
。
2 聯(lián)合體位域
2.1 聯(lián)合體(union)
首先我們來看一下什么是聯(lián)合體:
union Teacher {
char *name;
int age;
double height ;
};
這就是一個(gè)聯(lián)合體骤坐。
- 聯(lián)合體(union)的特點(diǎn)是:各個(gè)變量之間是互斥的绪杏,也就是說只能給其中一個(gè)變量賦值下愈;其優(yōu)點(diǎn)是內(nèi)存使用更為精細(xì)靈活纽绍,也節(jié)省了內(nèi)存空間。
- 結(jié)構(gòu)體(struct)的特點(diǎn)是:各個(gè)變量之間是共存的势似,也就是說可以同時(shí)給多個(gè)變量賦值拌夏;其缺點(diǎn)是內(nèi)存空間使用更為粗放,不管用不用履因,內(nèi)存權(quán)分配障簿。
下面我們通過代碼來看一下聯(lián)合體:
2.2 位域
舉個(gè)例子,我們有下面一個(gè)結(jié)構(gòu)體:
struct Car1 {
BOOL front;
BOOL back;
BOOL left;
BOOL right;
};
此時(shí)Car1
的大小是4個(gè)字節(jié):
此時(shí)就有一個(gè)問題栅迄,實(shí)際我們并不需要這么多內(nèi)存空間站故,我們只需要一個(gè)字節(jié)就可以完整的表達(dá)Car1
的意思。
為了達(dá)到這個(gè)效果毅舆,這個(gè)時(shí)候我們只需要在結(jié)構(gòu)體定義的時(shí)候西篓,指定成員變量所占的二進(jìn)制位數(shù)(Bit),這就是位域:
struct Car2 {
BOOL front;
BOOL back : 1;
BOOL left : 6;
BOOL right: 4;
};
:
用來限定成員變量占用的位數(shù)憋活;front
沒有限制岂津,根據(jù)類型推算其站1個(gè)字節(jié)(Byte);back
悦即,left
吮成,right
被:
后面的數(shù)字限制,不能根據(jù)數(shù)據(jù)類型計(jì)算長(zhǎng)度辜梳,其分別占用1(Bit)
粱甫,6(Bit)
,4(Bit)
的內(nèi)存作瞄。
3 isa 與 Class之間的關(guān)聯(lián)
3.1 isa_t
我們之前在分析alloc
流程的時(shí)候魔种,在_class_createInstanceFromZone
中有這樣一段代碼:
跟進(jìn)去:
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 {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// 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;
}
我們通過上面的代碼會(huì)發(fā)現(xiàn):
-
isa = newisa;
; -
newisa
是isa_t
類型的粉洼。
那么我們跟進(jìn)isa_t
节预,可以發(fā)現(xiàn)isa_t
是一個(gè)聯(lián)合體:
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);
};
我們通常在定義一個(gè)類對(duì)象的時(shí)候是不是這樣定義的Person *p = [[Person alloc] init]
叶摄,其中對(duì)象p
也叫做指針;指針有8字節(jié)(Byte)
-- 8 * 8 = 64(bit 位)
安拟,如果這64
只是存儲(chǔ)一個(gè)指針地址就會(huì)產(chǎn)生浪費(fèi)蛤吓。
由于每個(gè)類都有一個(gè)isa
,于是就在isa
里面存儲(chǔ)了一個(gè)類相關(guān)信息糠赦,比如:是否正在釋放会傲、引用計(jì)數(shù)、weak拙泽、關(guān)聯(lián)對(duì)象淌山、析構(gòu)函數(shù)等等。
isa_t
是一個(gè)聯(lián)合體顾瞻,那么我們要確認(rèn)其其中存儲(chǔ)的內(nèi)存泼疑,一般來說,我們要去看位域荷荤,于是我們?cè)?code>ISA_BITFIELD中找到了這個(gè):
這是一個(gè)宏退渗,其含義如下(注:有兩個(gè)不同環(huán)境下的宏,下面會(huì)做介紹):
變量名 | 值的含義 |
---|---|
nonpointer |
表示是否對(duì)isa 指針開啟指針優(yōu)化 蕴纳。0 :純isa 指針会油;1 :不只是類對(duì)象地址,isa 中包含了類信息古毛,對(duì)象的引用計(jì)數(shù)等等翻翩。 |
has_assoc |
關(guān)聯(lián)對(duì)象標(biāo)志位 0 :沒有;1 :存在稻薇。 |
has_cxx_dtor |
該對(duì)象是否有C++ 或者Objc 的析構(gòu)器嫂冻,如果有析構(gòu)函數(shù),則需要坐析構(gòu)邏輯颖低,如果沒有絮吵,則可以更快的釋放對(duì)象。 |
shiftcls |
存儲(chǔ)類指針的值忱屑。開啟指針優(yōu)化的情況下蹬敲,在arm64 架構(gòu)中,有33位用于存儲(chǔ)類指針莺戒。 |
magic |
用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象伴嗡,還是沒有初始化的空間。 |
weakly_referenced |
用于表示對(duì)象是否被指向或曾將指向一個(gè)ARC的若變量从铲,沒有弱引用的對(duì)象可以更快釋放瘪校。 |
deallocating |
標(biāo)志對(duì)象是否正在釋放內(nèi)存。 |
has_sidetable_rc |
當(dāng)對(duì)象引用計(jì)數(shù)大于10的時(shí)候,則需要家用該變量存儲(chǔ)進(jìn)位阱扬。 |
extra_rc |
表示該對(duì)象的引用計(jì)數(shù)數(shù)值泣懊,實(shí)際上是引用計(jì)數(shù)值減1。例如:如果對(duì)象的引用計(jì)數(shù)為10麻惶,那么extra_rc 為9馍刮;如果引用計(jì)數(shù)大于10,則需要用到上面的has_sidetable_rc 窃蹋。 |
其中x86_64
和arm64
兩種架構(gòu)中卡啰,ISA_BITFIELD
的對(duì)比如下(圖片來源isa與類關(guān)聯(lián)的原理):
3.2 isa與Class地址的關(guān)系
我們?cè)趺赐ㄟ^isa
指針,得到對(duì)應(yīng)的類地址呢警没,注意上面有一個(gè)ISA_MASK
宏匈辱,我們用isa & ISA_MASK
就可以得到類地址,如下:
為什么要&
一下呢杀迹,因?yàn)锳pple并不想讓我們直接得到對(duì)應(yīng)的值亡脸,也就是說,不想讓值直接明文暴露出來佛南。所以加了一個(gè)掩碼來配合一下梗掰。
3.3 initIsa
在initIsa
中嵌言,如果nonpointer == 0(純isa指針)
嗅回,那就isa = isa_t((uintptr_t)cls);
;否則就進(jìn)行一系列bit
的賦值(位域的賦值):
4 isa 的Class 的賦值反過程
這里我們利用位運(yùn)算來得到我們想要的Class地址摧茴。
我們首先來回顧一下上面講到的x86_64
的ISA_BITFIELD
:
我們要找的就是shifcls
绵载;而shifcls
右邊是3個(gè)bit
,左邊是17個(gè)bit
苛白。那么我們先右移3位娃豹,再左移20位,最后右移17位就可以得到shifcls
购裙。
具體操作如下: