第三節(jié)課 OC對(duì)象原理(下)
全篇開始之前我們想一個(gè)問題,研究了這么久對(duì)象,究竟什么是對(duì)象呢??
對(duì)象本質(zhì)以及拓展
Clang
探索對(duì)象的本質(zhì)前,我們先了解一個(gè)編譯器:clang
Clang是一個(gè)C語言、C++、OC語言的輕量級(jí)編譯器。源代碼發(fā)布于BSD協(xié)議下。Clang將支持其普通lambda表達(dá)式、返回類型的簡化處理以及更好的處理constexpr關(guān)鍵字。
clang是一個(gè)由Apple主導(dǎo)編寫,基于LLVM的C/C++/OC的編譯器
探索本質(zhì)
1、在main中自定義一個(gè)類LGPerson,有一個(gè)屬性name
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LGPerson
@end
2、通過終端,利用clang將main.m編譯成 main.cpp
clang -rewrite-objc main.m -o main.cpp
3、打開編譯好的main.cpp,找到LGPerson的定義,發(fā)現(xiàn)LGPerson在底層會(huì)被編譯成struct 結(jié)構(gòu)體

我們可以看到,這是一個(gè)結(jié)構(gòu)體,里面嵌套了一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體能夠繼承嘛?其實(shí)是可以的,但是是屬于
偽繼承,偽繼承的方式是直接將NSObject結(jié)構(gòu)體定義為LGPerson中的第一個(gè)屬性,意味著LGPerson擁有NSObject中的所有成員變量。
LGPerson_IMPL中的第一個(gè)屬性,其實(shí)就是isa,是繼承自NSObject,

通過上圖我們可以看到成員變量是Class isa。通常叫isa叫做isa指針,那么這里的Class應(yīng)該是個(gè)指針類型,在main.cpp文件中全局搜索*Class。代碼如下

LGPerson的類型是objc_object,在OC層面 我們的LGPerson是繼承NSObject,其實(shí)在下層真正的實(shí)現(xiàn)就是objc_object。

對(duì)象的本質(zhì)拓展
在剛才看class的過程中,還發(fā)現(xiàn)了兩個(gè)東西

熟悉的id和SEL。常用的id原來是一個(gè)objc_object結(jié)構(gòu)體指針,這就解釋了id修飾變量和作為返回值的時(shí)候?yàn)槭裁床患?code>*,下面的SEL也是結(jié)構(gòu)體指針這個(gè)也是之前不知道的呢,就稍微了解下吧~
在稍下面一點(diǎn)我們看到一些奇奇怪怪的一些東西

這其實(shí)就是我們的
Get方法和Set方法,但是參數(shù)部分呢?我們可在OC中沒有看到,這其實(shí)就 是我們的隱藏參數(shù)。這個(gè)
Get方法中的參數(shù)self+OBJC_IVAR_$_LGPerson$_name這一步就是我們之前講過的,要獲取一個(gè)類的對(duì)象的地址,是通過獲取類的首地址+對(duì)象的偏移量的方法來最終取得對(duì)象的。
小結(jié)
所以從上述探索過程中可以得出:
-
OC對(duì)象的本質(zhì)其實(shí)就是結(jié)構(gòu)體 -
LGPerson中的isa是繼承自NSObject中的isa
聯(lián)合體位域拓展補(bǔ)充
位域(位段)
位域:在C語言中允許在一個(gè)結(jié)構(gòu)體中以位為單位來指定其成員所占內(nèi)存長度,這種以位為單元的成員稱為位域。
struct HZMCar1 {
BOOL front; // 0 1
BOOL back;
BOOL left;
BOOL right;
};
struct HZMCar1 car1;
NSLog(@"%ld",sizeof(car1));
<--打印輸出-->
4
在開發(fā)當(dāng)中遇到這種情況,其實(shí)BOOL就只有兩種情況,但是卻使用了4個(gè)字節(jié)32位,其實(shí)我們只用4位就可以了,這樣我們就只使用了1字節(jié),還有3字節(jié)其實(shí)是浪費(fèi)的。接下來我們進(jìn)行改進(jìn)一下
struct HZMCar2 {
BOOL front: 1;
BOOL back : 1;
BOOL left : 1;
BOOL right: 1;
};
struct HZMCar2 car2;
NSLog(@"%ld",sizeof(car2));
<--打印輸出-->
1
其實(shí)就是指定對(duì)象所占內(nèi)存長度
聯(lián)合體(共用體)
struct LGStudent {
char *name;
int age;
double height ;
};
union LGStudent2 {
char *name;
int age;
double height ;
};
struct LGStudent student;
student.name = "HZM";
student.age = 18;
union LGStudent2 student2;
student2.name = "HZM";
student2.age = 18;
NSLog(@"%ld-%ld",sizeof(student),sizeof(student2));
<--打印輸出-->
24-8
首先我們能觀察到,同樣的成員變量,內(nèi)存大小天差地別,這是為什么呢?我們來通過斷點(diǎn)查看下。

可以看到聯(lián)合的成員變量在
未賦值的情況下是一塊臟數(shù)據(jù)、臟內(nèi)存,當(dāng)?shù)诙€(gè)變量被賦值的時(shí)候又將第一個(gè)變量清理了,所以聯(lián)合體的各種變量互斥,每次只能賦值一個(gè),這樣就可以接受大幅空間,這也就是我們之前看到的為什么聯(lián)合體的內(nèi)存大小比較小的原因。
小結(jié):
位域和聯(lián)合體對(duì)比
位域:優(yōu)點(diǎn)是所有變量可以共存,缺點(diǎn)是內(nèi)存空間粗放,不管用不用,都分配
聯(lián)合體:優(yōu)點(diǎn)是內(nèi)存更精細(xì)靈活,節(jié)省空間,缺點(diǎn)是各種變量互斥
isa的類型 isa_t
以下是isa指針的類型isa_t的定義,從定義中可以看出是通過聯(lián)合體(union)定義的。
union isa_t { //聯(lián)合體
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,兩者是互斥關(guān)系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa_t類型使用聯(lián)合體的原因也是基于內(nèi)存優(yōu)化的考慮,這里的內(nèi)存優(yōu)化是指在isa指針中通過char + 位域(即二進(jìn)制中每一位均可表示不同的信息)的原理實(shí)現(xiàn)。通常來說,isa指針占用的內(nèi)存大小是8字節(jié),即64位,已經(jīng)足夠存儲(chǔ)很多的信息了,這樣可以極大的節(jié)省內(nèi)存,以提高性能
從isa_t的定義中可以看出:
-
提供了兩個(gè)成員,
cls和bits,由聯(lián)合體的定義所知,這兩個(gè)成員是互斥的,也就意味著,當(dāng)初始化isa指針時(shí),只有一個(gè)變量有值- 通過
cls初始化,bits無默認(rèn)值 - 通過
bits初始化,cls有默認(rèn)值
- 通過
-
還提供了一個(gè)結(jié)構(gòu)體定義的
位域,用于存儲(chǔ)類信息及其他信息,結(jié)構(gòu)體的成員ISA_BITFIELD,這是一個(gè)宏定義,有兩個(gè)版本__arm64__(對(duì)應(yīng)ios 移動(dòng)端) 和__x86_64__(對(duì)應(yīng)macOS),以下是它們的一些宏定義,如下圖
03-ISA_BITFIELD.png
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:開啟指針優(yōu)化的情況下,在arm64架構(gòu)中有33位用來存儲(chǔ)類指針,x86_64中占 44位
magic:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間
weakly_referenced:標(biāo)志對(duì)象是否被指向或者曾經(jīng)指向一個(gè)ARC的弱變量,
沒有弱引用的對(duì)象可以更快釋放。
deallocating:標(biāo)志對(duì)象是否正在釋放內(nèi)存
has_sidetable_rc:當(dāng)對(duì)象引用計(jì)數(shù)大于 10 時(shí),則需要借用該變量存儲(chǔ)進(jìn)位
extra_rc:當(dāng)表示該對(duì)象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)值減 1
例如,如果對(duì)象的引用計(jì)數(shù)為 10,那么 extra_rc 為 9。如果引用計(jì)數(shù)大于 10, 則需要使用到上面的 has_sidetable_rc。

ISA_MASK
ISA_MASK是一個(gè)宏,一個(gè)掩碼。__x86_64__ 的值等于 0x00007ffffffffff8ULL,__arm64__ 的值等于0x0000000ffffffff8ULL。通過x/4g p獲取到的首位地址就是isa的值是0x001d800100008275,驗(yàn)證下0x001d800100008275&0x00007ffffffffff8ULL結(jié)果

對(duì)象通過 isa & 掩碼 得到類的信息
isa的位運(yùn)算
通過上面的學(xué)習(xí)我們已經(jīng)知道了isa的shiftcls是用來存儲(chǔ)類指針,所以我們降妖獲取類指針其實(shí)也可以通過位運(yùn)算來獲取shiftcls的最終值。

通過上圖我們發(fā)現(xiàn),其實(shí)我們要獲取的shiftcls正好在中間,左邊有3位,右邊有28位,那我們?nèi)绻@取的話就可以將shiftcls整體向左移3位,將多余部分?jǐn)D出去,向右移20位,將左邊的多余部分?jǐn)D出去,最后左移17位恢復(fù)原位,這個(gè)時(shí)候剩下的不就是我們需要的shiftcls了。我們來驗(yàn)證下

可以看到位運(yùn)算結(jié)束后的結(jié)果與我們輸出的class相同。
