OC底層原理(二):實(shí)例對(duì)象棒卷、類對(duì)象、元類對(duì)象

在OC中碧绞,對(duì)象分為三種

  • instance對(duì)象(實(shí)例對(duì)象)
  • class對(duì)象(類對(duì)象)
  • meta-class對(duì)象(元類對(duì)象)

instance對(duì)象

instance對(duì)象就是類通過alloc出來的對(duì)象府框,每次alloc都會(huì)產(chǎn)生一個(gè)新的 instance對(duì)象。

NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];

在上面的代碼中讥邻,obj1 和 obj2就是兩個(gè)不同的實(shí)例對(duì)象寓免。
instance對(duì)象中存儲(chǔ)著isa和其他的成員變量

@interface ZJPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, assign) int weight;
@end
ZJPerson *person = [[ZJPerson alloc]init];
person.age = 19;
person.height = 179;
person.weight = 69;

在上面的例子中,
person這個(gè)實(shí)例對(duì)象就存儲(chǔ)著 isa 以及 _age, _height, _weight四個(gè)變量


instance對(duì)象.png

類對(duì)象

每個(gè)類有且只有一個(gè)類對(duì)象
通過

+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");

或者通過runtime函數(shù)

object_getClass(id _Nullable obj) ;

來獲取

NSLog(@"%p --- %p", [ZJPerson class], object_getClass(person));

類對(duì)象里存儲(chǔ)著

  • isa 指針
  • superClass 指針
  • 屬性 信息
  • 實(shí)例方法 信息
  • 協(xié)議 信息
  • 成員變量 信息
class對(duì)象.png

此處的成員變量和實(shí)例對(duì)象中的成員變量不太一樣计维,實(shí)例對(duì)象中的成員變量記錄的是值,而類對(duì)象里的成員變量記錄的是成員變量的類型以及名字撕予。


元類對(duì)象

每個(gè)類有且只有一個(gè)元類對(duì)象
通過runtime函數(shù)

object_getClass(id _Nullable obj) ;

來獲取鲫惶,只不過參數(shù)傳類對(duì)象

//實(shí)例對(duì)象
ZJPerson *person = [[ZJPerson alloc]init];
//類對(duì)象
Class class = object_getClass(person);
//元類對(duì)象
Class metaClass = object_getClass(class);

元類對(duì)象里存儲(chǔ)著

  • isa 指針
  • superClass 指針
  • 類方法信息
meta-class對(duì)象.png

那么這三者之間有什么聯(lián)系呢?

isa指針

我們繼續(xù)在ZJPerson類添加一個(gè)實(shí)例方法和一個(gè)類方法

@interface ZJPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, assign) int weight;
- (void)personInstanceTest;
+ (void)personClassTest;
@end

然后調(diào)用它們

ZJPerson *person = [[ZJPerson alloc]init];
[person personInstanceTest];
[ZJPerson personClassTest];

上面這調(diào)用方法的代碼轉(zhuǎn)換成C\C++偽代碼如下:

objc_msgSend(person, @selector(personInstanceTest));
objc_msgSend([ZJPerson class], @selector(personClassTest));

第一句我們可以看到給實(shí)例對(duì)象發(fā)送了消息实抡,調(diào)用了存儲(chǔ)在類對(duì)象里的方法
第二句可以看到給類對(duì)象發(fā)送了消息欠母,調(diào)用了存儲(chǔ)在元類對(duì)象里的方法
那么這又是怎么實(shí)現(xiàn)的呢

instance對(duì)象的isa指針指向class對(duì)象

當(dāng)調(diào)用personInstanceTest方法的時(shí)候欢策,instance對(duì)象通過isa指針找到class對(duì)象,然后再?gòu)腸lass對(duì)象里存儲(chǔ)的方法列表里找到方法來調(diào)用

class對(duì)象的isa指針指向meta-class對(duì)象

當(dāng)調(diào)用personClassTest方法的時(shí)候赏淌,class對(duì)象通過isa指針找到meta-class對(duì)象踩寇,然后再?gòu)膍eta-class對(duì)象里存儲(chǔ)的方法列表里找到方法來調(diào)用


isa指針.png

superClass指針

我們?cè)傩陆硪粋€(gè)類ZJStudent,繼承自ZJPerson

@interface ZJStudent : ZJPerson
- (void)studentInstanceTest;
+ (void)studentClassTest;
@end

然后初始化ZJStudent對(duì)象六水,調(diào)用ZJPerson的personInstanceTest方法和personClassTest方法

ZJStudent *student = [[ZJStudent alloc]init];
[student personInstanceTest];
[ZJStudent personClassTest];

那么上面這段代碼底層邏輯又該怎么走呢
當(dāng)調(diào)用personInstanceTest方法的時(shí)候俺孙,ZJStudent的instance對(duì)象通過isa指針找到ZJStudent的class對(duì)象,然后在ZJStudent的class對(duì)象存儲(chǔ)的方法列表中找personInstanceTest掷贾,發(fā)現(xiàn)沒有睛榄,就繼續(xù)通過superClass指針找到ZJPerson的class對(duì)象,然后再ZJPerson的class對(duì)象存儲(chǔ)中的方法列表里找到并調(diào)用
同理想帅,調(diào)用personClassTest方法的時(shí)候场靴,ZJStudent的class對(duì)象通過isa指針找到ZJStudent的meta-class對(duì)象,然后在ZJStudent的meta-class對(duì)象存儲(chǔ)的方法列表中找personClassTest港准,發(fā)現(xiàn)沒有旨剥,就繼續(xù)通過superClass指針找到ZJPerson的meta-class對(duì)象扒俯,然后再ZJPerson的meta-class對(duì)象存儲(chǔ)中的方法列表里找到并調(diào)用


class對(duì)象的superClass指針.png

meta-class對(duì)象的superClass指針.png

總結(jié)

isa和superClass.png
  1. instance的isa指向class
  2. class的isa指向meta-class
  3. meta-class的isa指向基類的meta-class
  4. class的superclass指向父類的class
    • 如果沒有父類毕骡,則superclass為nil
  5. meta-class的superclass指向父類的meta-class
    • 基類的meta-class的superclass指向基類的class
  6. instance方法調(diào)用軌跡
    • 通過instance的isa指針找到class愁憔,如果不存在就通過superclass指針往上找衬潦,直到為nil弛槐,拋出異常
  7. class方法調(diào)用軌跡
    • 通過class的isa指針找到meta-class关噪,如果不存在就通過superclass指針往上找衔憨,直到為nil严卖,拋出異常

補(bǔ)充

基類的meta-class對(duì)象的superclass指針指向基類的class對(duì)象
我們通過代碼來證明
首先創(chuàng)建一個(gè)類繼承自NSObject

@interface ZJPerson : NSObject
+ (void)test;
@end

@implementation ZJPerson
+ (void)test {
    NSLog(@"+[ZJPerson test] ---- %p", self);
}
@end

然后再創(chuàng)建一個(gè)NSObject的分類烟具,實(shí)現(xiàn)相同的類方法

@interface NSObject (test)
+ (void)test;
@end

@implementation NSObject (test)
+ (void)test {
    NSLog(@"+[NSObject test] ---- %p", self);
}
@end

最后再命令行項(xiàng)目的main函數(shù)中

NSLog(@"[ZJPerson class] --- %p", [ZJPerson class]);
NSLog(@"[NSObject class] --- %p", [NSObject class]);
[ZJPerson test];
[NSObject test];

輸出如下


截屏2020-12-17 21.21.49.png

可以看到
ZJPerson調(diào)用ZJPerson自己的test方法梢什,NSObject調(diào)用NSObject自己的test方法,沒啥問題
接下來朝聋,我們把ZJPerson的test方法注釋掉嗡午,再此運(yùn)行一次代碼看看輸出情況

@interface ZJPerson : NSObject
+ (void)test;
@end

@implementation ZJPerson
//+ (void)test {
//    NSLog(@"+[ZJPerson test] ---- %p", self);
//}
@end
截屏2020-12-17 21.22.55.png

可以看到[ZJPerson test]方法調(diào)用的是NSObject的test方法,這根據(jù)前面的調(diào)用邏輯來推理冀痕,調(diào)用test方法荔睹,給ZJPerson的class對(duì)象發(fā)送了消息,根據(jù)isa指針找到ZJPerson的meta-class言蛇,發(fā)現(xiàn)沒有此方法僻他,再根據(jù)superclass指針往上找到NSObject的meta-class,發(fā)現(xiàn)此方法腊尚,調(diào)用吨拗,也啥大問題。
再接下來我們把NSObject的+test方法注釋掉,新增一個(gè)-test方法

@implementation NSObject (test)
//+ (void)test {
//    NSLog(@"+[NSObject test] ---- %p", self);
//}

- (void)test {
    NSLog(@"-[NSObject test] ---- %p", self);
}
@end

再運(yùn)行一次再看輸出結(jié)果


截屏2020-12-17 21.25.06.png

可以看到我調(diào)用[ZJPerson test]方法劝篷,實(shí)際上調(diào)用的卻是NSObject的-test方法哨鸭,這是為什么呢?
我們可以梳理下邏輯
首先調(diào)用[ZJPerson test]娇妓,給ZJPerson的class對(duì)象發(fā)送調(diào)用test方法消息像鸡,然后class對(duì)象通過isa指針找到ZJPerson的meta-class對(duì)象,發(fā)現(xiàn)方法列表里沒有test方法哈恰,就通過superclass指針繼續(xù)往上找只估,找到NSObject的meta-class對(duì)象,發(fā)現(xiàn)它的方法列表里也沒有蕊蝗,就繼續(xù)通過superclass往上找仅乓,找到了NSObject的class對(duì)象,剛好有test方法蓬戚,調(diào)用


調(diào)用邏輯.png

再補(bǔ)充

instance對(duì)象里的isa存儲(chǔ)的并不是class對(duì)象的地址值夸楣,而是isa指針&ISA_MASK才是class對(duì)象的值

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#endif

截屏2020-12-17 21.55.50.png

因?yàn)槭莔ac的命令行項(xiàng)目,所以&的是x86_64下ISA_MASK的值
isa.png

面試題

  1. 對(duì)象的isa指針指向哪里子漩?
  • instance對(duì)象的isa指向class對(duì)象
  • class對(duì)象的isa指向meta-class對(duì)象
  • meta-class對(duì)象的isa指向基類的meta-class對(duì)象
  1. OC的類信息存放在哪里豫喧?
  • 成員變量具體的值,存放在instance對(duì)象中
  • 成員變量幢泼、協(xié)議紧显、實(shí)例方法、屬性缕棵,存放在class對(duì)象中
  • 類方法孵班,存放在meta-class對(duì)象中
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市招驴,隨后出現(xiàn)的幾起案子篙程,更是在濱河造成了極大的恐慌,老刑警劉巖别厘,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虱饿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡触趴,警方通過查閱死者的電腦和手機(jī)氮发,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冗懦,“玉大人爽冕,你說我怎么就攤上這事∨叮” “怎么了扇售?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵前塔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我承冰,道長(zhǎng),這世上最難降的妖魔是什么食零? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任困乒,我火速辦了婚禮,結(jié)果婚禮上贰谣,老公的妹妹穿的比我還像新娘娜搂。我一直安慰自己,他們只是感情好吱抚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布百宇。 她就那樣靜靜地躺著,像睡著了一般秘豹。 火紅的嫁衣襯著肌膚如雪携御。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天既绕,我揣著相機(jī)與錄音啄刹,去河邊找鬼。 笑死凄贩,一個(gè)胖子當(dāng)著我的面吹牛誓军,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疲扎,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昵时,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了椒丧?” 一聲冷哼從身側(cè)響起壹甥,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瓜挽,沒想到半個(gè)月后盹廷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡久橙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年俄占,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淆衷。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缸榄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祝拯,到底是詐尸還是另有隱情甚带,我是刑警寧澤她肯,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站鹰贵,受9級(jí)特大地震影響晴氨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碉输,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一籽前、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敷钾,春花似錦枝哄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至侨赡,卻和暖如春蓖租,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辆毡。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工菜秦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舶掖。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓球昨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親眨攘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子主慰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容