第三節(jié)課 OC對象原理(下)
全篇開始之前我們想一個問題裹唆,研究了這么久對象,究竟什么是對象
呢?舍咖?
對象本質以及拓展
Clang
探索對象的本質前,我們先了解一個編譯器:clang
Clang是一個C語言锉桑、C++排霉、OC語言的輕量級編譯器。源代碼發(fā)布于BSD協(xié)議下民轴。Clang將支持其普通lambda表達式攻柠、返回類型的簡化處理以及更好的處理constexpr關鍵字。
clang是一個由Apple主導編寫后裸,基于LLVM的C/C++/OC的編譯器
探索本質
1瑰钮、在main
中自定義一個類LGPerson
,有一個屬性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
在底層會被編譯成struct 結構體
我們可以看到,這是一個結構體扶檐,里面嵌套了一個結構體梁呈,結構體能夠繼承嘛?其實是可以的蘸秘,但是是屬于
偽繼承
官卡,偽繼承的方式是直接將NSObject
結構體定義為LGPerson
中的第一個屬性
,意味著LGPerson擁有NSObject中的所有成員變量醋虏。
LGPerson_IMPL
中的第一個屬性寻咒,其實就是isa
,是繼承自NSObject
颈嚼,
通過上圖我們可以看到成員變量是Class isa
毛秘。通常叫isa
叫做isa指針
,那么這里的Class
應該是個指針類型
阻课,在main.cpp文件中全局搜索*Class叫挟。代碼如下
LGPerson
的類型是objc_object
,在OC層面 我們的LGPerson是繼承NSObject
限煞,其實在下層真正的實現(xiàn)就是objc_object
抹恳。
對象的本質拓展
在剛才看class的過程中,還發(fā)現(xiàn)了兩個東西
熟悉的id
和SEL
奋献。常用的id
原來是一個objc_object
結構體指針健霹,這就解釋了id
修飾變量和作為返回值的時候為什么不加*
,下面的SEL
也是結構體指針這個也是之前不知道的呢瓶蚂,就稍微了解下吧~
在稍下面一點我們看到一些奇奇怪怪的一些東西
這其實就是我們的
Get方法
和Set方法
,但是參數(shù)部分呢窃这?我們可在OC中沒有看到瞳别,這其實就 是我們的隱藏參數(shù)。這個
Get
方法中的參數(shù)self
+OBJC_IVAR_$_LGPerson$_name
這一步就是我們之前講過的杭攻,要獲取一個類的對象的地址祟敛,是通過獲取類的首地址
+對象的偏移量
的方法來最終取得對象的。
小結
所以從上述探索過程中可以得出:
-
OC對象的本質
其實就是結構體
-
LGPerson
中的isa
是繼承
自NSObject中的isa
聯(lián)合體位域拓展補充
位域(位段)
位域:在C語言中允許在一個結構體中以位為單位來指定其成員所占內存長度朴上,這種以位為單元的成員稱為位域。
struct HZMCar1 {
BOOL front; // 0 1
BOOL back;
BOOL left;
BOOL right;
};
struct HZMCar1 car1;
NSLog(@"%ld",sizeof(car1));
<--打印輸出-->
4
在開發(fā)當中遇到這種情況卒煞,其實BOOL就只有兩種情況痪宰,但是卻使用了4個字節(jié)32位
,其實我們只用4位就可以
了畔裕,這樣我們就只使用了1字節(jié)衣撬,還有3字節(jié)其實是浪費的。接下來我們進行改進一下
struct HZMCar2 {
BOOL front: 1;
BOOL back : 1;
BOOL left : 1;
BOOL right: 1;
};
struct HZMCar2 car2;
NSLog(@"%ld",sizeof(car2));
<--打印輸出-->
1
其實就是指定對象所占內存長度
聯(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
首先我們能觀察到扮饶,同樣的成員變量具练,內存大小天差地別,這是為什么呢甜无?我們來通過斷點查看下扛点。
可以看到聯(lián)合的成員變量在
未賦值的情況下是一塊臟數(shù)據、臟內存
岂丘,當?shù)诙€變量被賦值的時候又將第一個變量清理了陵究,所以聯(lián)合體的各種變量互斥
,每次只能賦值一個奥帘,這樣就可以接受大幅空間铜邮,這也就是我們之前看到的為什么聯(lián)合體的內存大小比較小的原因。
小結:
位域和聯(lián)合體對比
位域:優(yōu)點是所有變量可以共存
寨蹋,缺點是內存空間粗放
松蒜,不管用不用,都分配
聯(lián)合體:優(yōu)點是內存更精細靈活已旧,節(jié)省空間
秸苗,缺點是各種變量互斥
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 难述,兩者是互斥關系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa_t
類型使用聯(lián)合體的原因也是基于內存優(yōu)化的考慮萤晴,這里的內存優(yōu)化是指在isa指針中通過char + 位域
(即二進制中每一位均可表示不同的信息)的原理實現(xiàn)。通常來說胁后,isa指針
占用的內存大小是8
字節(jié)店读,即64
位,已經足夠存儲很多的信息了攀芯,這樣可以極大的節(jié)省內存屯断,以提高性能
從isa_t的定義中可以看出:
-
提供了兩個成員,
cls
和bits
侣诺,由聯(lián)合體的定義所知殖演,這兩個成員是互斥
的,也就意味著年鸳,當初始化isa指針時趴久,只有一個變量有值- 通過
cls
初始化,bits無默認值
- 通過
bits
初始化搔确,cls有默認值
- 通過
-
還提供了一個結構體定義的
位域
彼棍,用于存儲類信息及其他信息,結構體的成員ISA_BITFIELD
膳算,這是一個宏定義
座硕,有兩個版本__arm64__
(對應ios 移動端) 和__x86_64__
(對應macOS),以下是它們的一些宏定義涕蜂,如下圖
nonpointer
:表示是否對isa
指針開啟指針優(yōu)化
0:純isa
指針华匾,1:不止是類對象地址,isa
中包含了類信息机隙、對象的引用計數(shù)等
has_assoc
:關聯(lián)對象標志位
0:沒有蜘拉,1:存在
has_cxx_dtor
:該對象是否有C++
或者Objc
的析構器,如果有析構函數(shù)有鹿,則需要做析構邏輯诸尽,如果沒有,則可以更快的釋放對象
shiftcls
:開啟指針優(yōu)化的情況下印颤,在arm64
架構中有33
位用來存儲類指針您机,x86_64
中占 44
位
magic
:用于調試器判斷當前對象是真的對象
還是沒有初始化的空間
weakly_referenced
:標志對象是否被指向
或者曾經指向一個ARC的弱變量
,
沒有弱引用的對象可以更快釋放年局。
deallocating
:標志對象是否正在釋放內存
has_sidetable_rc
:當對象引用計數(shù)大于 10 時
际看,則需要借用該變量存儲進位
extra_rc
:當表示該對象的引用計數(shù)值
,實際上是引用計數(shù)值減 1
例如矢否,如果對象的引用計數(shù)為 10
仲闽,那么 extra_rc
為 9
。如果引用計數(shù)大于 10
僵朗, 則需要使用到上面的 has_sidetable_rc
赖欣。
ISA_MASK
ISA_MASK
是一個宏,一個掩碼屑彻。__x86_64__
的值等于 0x00007ffffffffff8ULL
,__arm64__
的值等于0x0000000ffffffff8ULL
顶吮。通過x/4g p
獲取到的首位地址就是isa
的值是0x001d800100008275
社牲,驗證下0x001d800100008275&0x00007ffffffffff8ULL結果
對象通過 isa & 掩碼 得到類的信息
isa的位運算
通過上面的學習我們已經知道了isa
的shiftcls
是用來存儲類指針
,所以我們降妖獲取類指針其實也可以通過位運算
來獲取shiftcls的最終值
悴了。
通過上圖我們發(fā)現(xiàn)搏恤,其實我們要獲取的shiftcls
正好在中間,左邊有3位湃交,右邊有28位熟空,那我們如果要獲取的話就可以將shiftcls
整體向左移3位
,將多余部分擠出去搞莺,向右移20位
息罗,將左邊的多余部分擠出去,最后左移17位恢復原位
才沧,這個時候剩下的不就是我們需要的shiftcls
了迈喉。我們來驗證下
可以看到位運算結束后的結果與我們輸出的class相同。