類與對(duì)象的關(guān)系
關(guān)于類和對(duì)象的關(guān)系,對(duì)于它的理解可能停留在對(duì)象是類創(chuàng)建(alloc,new)
出來(lái)的這種很淺層的關(guān)系上局服,如果要深挖出背后的秘密葛超,還是得從地址和內(nèi)存入手官扣,接下來(lái)就一步步探索。
step1:
創(chuàng)建一個(gè)繼承于NSObject
的LBHPerson
類
//.h 文件
@interface LBHPerson : NSObject
@end
//.m 文件
#import "LBHPerson.h"
@implementation LBHPerson
@end
step2:
在main.m
在導(dǎo)入#import "LBHPerson.h"
頭文件芙委,并創(chuàng)建一個(gè)person
對(duì)象乞旦,并打上斷點(diǎn)
step3:
通過(guò)lldb
輸出person
相關(guān)信息
lldb命令在內(nèi)存對(duì)齊 一文中講過(guò)
通過(guò)isa
獲取獲取類信息
在isa與類關(guān)聯(lián)的原理 一文中講過(guò)
0x001d8001000081d1 & 0x00007ffffffffff8ULL
繼續(xù)通過(guò)
step3
中類的isa
獲取類信息得到的是什么呢?
step4:
通過(guò)類的isa
獲取對(duì)應(yīng)的類(元類)
我們會(huì)發(fā)現(xiàn)po 0x0000000105d84c70
與po 0x0000000105d84c48
得到的結(jié)果是一樣的题山,都是LBHPerson
兰粉,兩個(gè)不同的地址指向同一個(gè)類,0x0000000105d84c48
是類0x0000000105d84c70
的isa
通過(guò)獲取類信息
得到的顶瞳,我們稱之為元類
玖姑。
元類的說(shuō)明
我們知道 對(duì)象的isa
指向類
,其實(shí)類
也是一個(gè)對(duì)象
慨菱,可以稱為類對(duì)象
焰络,其isa
的位域指向蘋果定義的元類
-
元類
是系統(tǒng)
給的,其定義
和創(chuàng)建
都是由編譯器
完成符喝,在這個(gè)過(guò)程中闪彼,類的歸屬來(lái)自于元類
-
元類
是類對(duì)象
的類
,每個(gè)類都有一個(gè)獨(dú)一無(wú)二的元類用來(lái)存儲(chǔ) 類方法的相關(guān)信息协饲。 -
元類
本身是沒(méi)有名稱
的畏腕,由于與類相關(guān)聯(lián),所以使用了同類名一樣的名稱
step5:
繼續(xù)通過(guò)元類
的isa
指向 根元類
元類
的isa
指向根元類NSObject
step6:
根元類
的isa
指向茉稠?
根元類
的isa
指向自己
可以得出一個(gè)關(guān)系鏈:對(duì)象
--> 類
--> 元類
--> NSObject
, NSObject 指向自身
一個(gè)類在內(nèi)存中會(huì)不會(huì)存在多份描馅?
以LBHPerson
為例
通過(guò)對(duì)象person
的isa
獲取到的類信息地址
與LBHPerson.class
獲取到的地址是相同的,這意味一個(gè)類在內(nèi)存中不會(huì)存在多份而线,這個(gè)結(jié)論是否正確呢铭污?我們來(lái)驗(yàn)證一下:
way1:
通過(guò)幾種獲取類對(duì)象的方式
Class class1 = [LBHPerson class];
Class class2 = [LBHPerson alloc].class;
Class class3 = object_getClass([LBHPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p", class1, class2, class3);
運(yùn)行結(jié)果
三種方式獲取到的類對(duì)象地址相同恋日,即類在內(nèi)存中只有一份
isa走位與繼承關(guān)系圖
isa關(guān)系
-
示例對(duì)象
的isa指針類對(duì)象
-
類對(duì)象
的isa指向元類對(duì)象
-
元類對(duì)象
的isa指向根元類
-
根元類
的isa指向它自己本身
,從而形成了閉環(huán)
繼承關(guān)系
-
類對(duì)象的繼承關(guān)系
-
類
繼承于它的父類
-
父類
繼承它的父類
... - 直到找到
根類
嘹狞,也就是NSObject
-
NSObject
則繼承于nil
岂膳,這也就是所有的根源,即無(wú)中生有
-
-
元類的繼承關(guān)系
-
子類的元類
繼承與父類的元類
-
父類的元類
繼承它的父類的元類
... - 直到找到
根元類
- 而
根元類
則是繼承于NSObject
-
【注意】
實(shí)例對(duì)象之間沒(méi)有繼承關(guān)系磅网,類之間有繼承關(guān)系
舉例說(shuō)明
新建一個(gè)LBHTeacher
類繼承與LBHPerson
谈截,在main
函數(shù)中實(shí)例化
//.h
@interface LBHTeacher : LBHPerson
@end
//.m
#import "LBHTeacher.h"
@implementation LBHTeacher
@end
//main
LBHPerson *person = [LBHPerson alloc];
LBHTeacher *teacher = [LBHTeacher alloc];
它們對(duì)應(yīng)的isa
走位和繼承圖
-
isa 走位鏈(兩條)
teacher的isa走位鏈:
teacher(子類對(duì)象)
-->LBHTeacher(子類)
-->LBHTeacher(子元類)
-->NSObject(根元類)
-->NSObject(根元類,即自己)
person的isa走位鏈:
person(父類對(duì)象)
-->LBHPerson(父類)
-->LBHPerson(父元類)
-->NSObject(根元類)
-->NSObject(根元類知市,即自己)
-
superclass繼承鏈(兩條)
類的繼承關(guān)系鏈:
LBHTeacher(子類)
-->LBHPerson(父類)
-->NSObject(根類)
-->nil
元類的繼承關(guān)系鏈:
LBHTeacher(子元類)
-->LBHPerson(父元類)
-->NSObject(根元類)
-->NSObject(根類)
-->nil
對(duì)象的本質(zhì)
對(duì)象的本質(zhì)其實(shí)就是結(jié)構(gòu)體,而編譯到底層會(huì)發(fā)現(xiàn)包含一個(gè)objc_class
結(jié)構(gòu)體類型的變量
為什么
對(duì)象
和類
都有isa
屬性呢速蕊?
在在isa與類關(guān)聯(lián)的原理 一文中使用clang
編譯過(guò)main.m
文件嫂丙,從編譯后的c++文件中可以看到如下c++源碼:
//LGPerson的底層編譯
struct LBHPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
//NSObject 的底層編譯
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
在c++底層OC層面的繼承
實(shí)際上是子類結(jié)構(gòu)體
包含一個(gè)父類結(jié)構(gòu)體
作為成員變量
我們對(duì)c++源碼進(jìn)一步進(jìn)行分析
step1:
在objc4源碼中 搜索objc_class
通過(guò)源碼可以看出 objc_class
其實(shí)就是繼承自 objc_object
。
step2:
在objc4源碼中 搜索objc_object
objc_class
與objc_object
有什么關(guān)系规哲?
- 結(jié)構(gòu)體類型
objc_class
繼承自objc_object
類型跟啤,其中objc_object
也是一個(gè)結(jié)構(gòu)體,且有一個(gè)isa
屬性唉锌,所以objc_class
也擁有了isa
屬性 - mian.cpp底層編譯文件中隅肥,
NSObject
中的isa
在底層是由Class
定義的,其中class
的底層編碼來(lái)自objc_class
類型袄简,所以NSObject
也擁有了isa
屬性
objc_object
與對(duì)象
的關(guān)系 (百度面試題)
- 對(duì)象繼承于
objc_object
腥放,而objc_class
繼承于objc_object
,所以對(duì)象繼承與objc_object
總結(jié)
- 所有的
對(duì)象
+類
+元類
都有isa
屬性 - 所有的
對(duì)象
都是由objc_object
繼承來(lái)的 - 簡(jiǎn)單概括就是
萬(wàn)物皆對(duì)象
绿语,萬(wàn)物皆來(lái)源于objc_object
秃症,有以下兩點(diǎn)結(jié)論:- 所有以
objc_object
為模板 創(chuàng)建的對(duì)象
,都有isa
屬性 - 所有以
objc_class
為模板吕粹,創(chuàng)建的類
种柑,都有isa
屬性
- 所有以
- 在結(jié)構(gòu)層面可以通俗的理解為
上層OC
與底層
的對(duì)接:- 下層是通過(guò)
結(jié)構(gòu)體
定義的模板
,例如objc_class匹耕、objc_object - 上層是通過(guò)底層的模板創(chuàng)建的 一些類型聚请,例如LBHPerson
- 下層是通過(guò)
objc_class
、objc_object
稳其、isa
驶赏、object
、NSObject
的關(guān)系如圖所示:
類結(jié)構(gòu)的分析
探索類結(jié)構(gòu)
時(shí)既鞠,我們并不是很清楚里面有些什么母市,但是我們可以通過(guò)類
得到一個(gè)首地址
,然后通過(guò)地址平移
去獲取里面所有的值
损趋。
前面我們已經(jīng)在objc4找到objc_class
源碼
struct objc_class : objc_object {
// Class ISA; //8字節(jié)
Class superclass; //Class 類型 8字節(jié)
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略患久,未貼出
}
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
對(duì)應(yīng)objc_class
的結(jié)構(gòu)圖
-
isa
屬性:繼承自objc_object
的isa
椅寺,是一個(gè)指針,占8字節(jié)
-
superclass
屬性:Class
類型蒋失,Class
是由objc_object
定義的返帕,是一個(gè)指針,占8字節(jié)
-
cache
屬性:從類型cache_t
目前無(wú)法得知篙挽,而cache_t
是一個(gè)結(jié)構(gòu)體類型荆萤,結(jié)構(gòu)體的內(nèi)存大小
需要根據(jù)內(nèi)部的屬性來(lái)確定
,而結(jié)構(gòu)體指針才是8字節(jié)
-
bits
屬性:只有首地址
經(jīng)過(guò)上面3個(gè)屬性的內(nèi)存大小總和的平移
铣卡,才能獲取到bits
計(jì)算cache大小
進(jìn)入cache_t
的定義
// 結(jié)構(gòu)體字節(jié)大小链韭,看里面的成員變量,而大部分都是 static 和方法都不存在結(jié)構(gòu)體里面
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 結(jié)構(gòu)體指針 8 字節(jié)
explicit_atomic<mask_t> _mask; //是mask_t 類型煮落,而 mask_t 是 unsigned int 的別名敞峭,占4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets; //是指針,占8字節(jié)
mask_t _mask_unused; //是mask_t 類型蝉仇,而 mask_t 是 uint32_t 類型定義的別名旋讹,占4字節(jié)
#if __LP64__
uint16_t _flags; //是uint16_t類型,uint16_t是 unsigned short 的別名轿衔,占 2個(gè)字節(jié)
#endif
uint16_t _occupied; //是uint16_t類型沉迹,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)
// 方法代碼過(guò)多害驹,自動(dòng)省略
...
};
可以得到cache
占16字節(jié)
bits
bits的位置
objc_class
中前三個(gè)屬性的大小為:8+8+16=32鞭呕,所以想獲取bits中的信息可以通過(guò)首地址偏移32字節(jié)獲得。
獲取bits內(nèi)容
先看下bits的結(jié)構(gòu)
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
// 代碼過(guò)多宛官,自動(dòng)省略
...
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 代碼過(guò)多琅拌,自動(dòng)省略
...
};
class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
// 省略過(guò)多的代碼
...
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
};
可以看出,我們可以通過(guò)class_rw_t
結(jié)構(gòu)體中提供的methods()
摘刑、properties()
进宝、protocols()
獲取到對(duì)應(yīng)的方法、屬性和協(xié)議枷恕。
既然知道可以獲取到對(duì)應(yīng)的方法党晋、屬性和協(xié)議,那就給LBHPerson
類添加一些方法和屬性
//.h
{
NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
- (void)test1;
+ (void)test2;
//.m
- (void)test1
{
NSLog(@"%s",__func__);
}
+ (void)test2
{
NSLog(@"%s",__func__);
}
接下來(lái)我們通過(guò)lldb
調(diào)試來(lái)查找對(duì)應(yīng)的class_data_bist_t bits
徐块,查看相應(yīng)的信息未玻。
- 其中的
data()
獲取數(shù)據(jù),是由objc_class
提供的方法
獲取屬性列表
-
p $3.properties()
命令中的properties
方法是由class_rw_t
提供的,方法中返回的實(shí)際類型為property_array_t
- 由于
list
的類型是property_list_t
胡控,是一個(gè)指針扳剿,所以通過(guò)p *$5
獲取內(nèi)存中的信息,同時(shí)也證明bits中存儲(chǔ)了property_list
昼激,即屬性列表
在獲取第二個(gè)屬性時(shí)出現(xiàn)報(bào)錯(cuò)庇绽,數(shù)組越界锡搜,我們定義的
nickName
呢?
這類補(bǔ)充下成員變量瞧掺、實(shí)例變量耕餐、屬性的區(qū)別?
成員變量
:在{ }
中所聲明的變量都是成員變量(實(shí)例變量是一種特殊的成員變量)
實(shí)例變量
:成員變量的一種辟狈,由類聲明的對(duì)象
屬性
:@property
修飾肠缔,編譯器會(huì)自動(dòng)生成setter和getter方法
獲取成員變量
在class_rw_t
結(jié)構(gòu)體中有個(gè)ro()
可以獲取成員變量
通過(guò)lldb
調(diào)試來(lái)查找成員變量
class_ro_t
結(jié)構(gòu)體中的屬性如下所示,想要獲取ivars
哼转,需要ro
的首地址
平移48字節(jié)
struct class_ro_t {
uint32_t flags; //4
uint32_t instanceStart;//4
uint32_t instanceSize;//4
#ifdef __LP64__
uint32_t reserved; //4
#endif
const uint8_t * ivarLayout; //8
const char * name; //1 ? 8
method_list_t * baseMethodList; // 8
protocol_list_t * baseProtocols; // 8
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
//方法省略
}
【總結(jié)】
- 通過(guò)
@property
定義的屬性明未,也會(huì)存儲(chǔ)在bits
屬性中,通過(guò)bits
-->data()
-->properties()
-->list
獲取屬性列表壹蔓,其中只包含屬性 - 通過(guò)
{}
定義的成員變量趟妥,會(huì)存儲(chǔ)在類的bits
屬性中,通過(guò)bits
-->data()
-->ro()
-->ivars
獲取成員變量列表庶溶,除了包括成員變量煮纵,還包括屬性定義的成員變量
方法列表 methods()
通過(guò)lldb
調(diào)試來(lái)查找方法列表
通過(guò)打印的count = 4
可知懂鸵,存儲(chǔ)了4個(gè)方法,可以通過(guò)p $7.get(i)
內(nèi)存偏移的方式獲取單個(gè)方法,i
的范圍是0-3
我們定義的類方法
+ (void)test2
好像遍歷并沒(méi)有找到偏螺,它存儲(chǔ)在哪呢?
類方法的存儲(chǔ)
在methods list
中并沒(méi)有找到類方法
匆光, 那類方法存儲(chǔ)在哪里套像?下面我們來(lái)分析下:
前面有分析元類
,元類
是用來(lái)存儲(chǔ)類
的相關(guān)信息
的终息,我們大膽猜測(cè)一下:類方法存儲(chǔ)在元類的bits中呢夺巩?通過(guò)lldb
命令來(lái)驗(yàn)證我們的猜測(cè):
根據(jù)打印我們可以知道猜測(cè)是正確的,類方法存儲(chǔ)在元類中周崭。
【總結(jié)】
-
類
的實(shí)例方法
存儲(chǔ)在類的bits
屬性中柳譬,通過(guò)bits
-->methods()
-->list
獲取實(shí)例方法列表; -
類
的類方法
存儲(chǔ)在元類的bits
屬性中续镇,通過(guò)元類bits
-->methods()
-->list
獲取類方法列表美澳。
類的結(jié)構(gòu)功能
名稱 | 類型 | 功能 |
---|---|---|
isa | 指針 | 指向元類的指針 |
superclass | 指針 | 指向當(dāng)前類的父類 |
cache | 結(jié)構(gòu)體 | 用于緩存方法的,用于加速方法的調(diào)用 |
bits | 結(jié)構(gòu)體 | 存儲(chǔ)類的方法摸航、屬性制跟、協(xié)議等信息的地方 |