每日一問08——runtime類與對(duì)象

前幾天看到老哥說他去面試散劫,面試官問他OC里類對(duì)象和實(shí)例對(duì)象有什么區(qū)別涨缚?不知道Objective-C對(duì)象模型的同學(xué)很可能搞不清楚面試官究竟想問什么蝙场。
本篇先講對(duì)象模型到底是什么樣的以及runtime中巷送,類是如何實(shí)現(xiàn)的衩匣。

Objective-C對(duì)象模型

我們打開<objc/objc.h>文件可以看到如下對(duì)NSObject的定義:

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

可以看到NSObject 就是一個(gè)包含 isa 指針的結(jié)構(gòu)體未檩,在 Objective-C 語言中戴尸,每一個(gè)類實(shí)際上也是一個(gè)對(duì)象。每一個(gè)類也有一個(gè)名為 isa 的指針冤狡。每一個(gè)類也可以接受消息孙蒙,例如[NSObject alloc]项棠,就是向 NSObject 這個(gè)類發(fā)送名為alloc消息。

再看一下class的定義

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

發(fā)現(xiàn)他里面也有一個(gè)isa指針挎峦,所以類也是一個(gè)對(duì)象香追,那它也必須是另一個(gè)類的實(shí)列,這個(gè)類就是元類 (metaclass)坦胶。元類保存了類方法的列表透典。當(dāng)一個(gè)類方法被調(diào)用時(shí),元類會(huì)首先查找它本身是否有該類方法的實(shí)現(xiàn)顿苇,如果沒有峭咒,則該元類會(huì)向它的父類查找該方法,直到一直找到繼承鏈的頭纪岁。

1.isa:需要注意的是在Objective-C中凑队,所有的類自身也是一個(gè)對(duì)象,這個(gè)對(duì)象的Class里面也有一個(gè)isa指針幔翰,它指向metaClass(元類)漩氨,我們會(huì)在后面介紹它。

2.super_class:指向該類的父類遗增,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy)叫惊,則super_class為NULL。

3.cache:用于緩存最近使用的方法做修。一個(gè)接收者對(duì)象接收到一個(gè)消息時(shí)赋访,它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對(duì)象。在實(shí)際使用中缓待,這個(gè)對(duì)象只有一部分方法是常用的蚓耽,很多方法其實(shí)很少用或者根本用不上。這種情況下旋炒,如果每次消息來時(shí)步悠,我們都是methodLists中遍歷一遍,性能勢(shì)必很差瘫镇。這時(shí)鼎兽,cache就派上用場(chǎng)了。在我們每次調(diào)用過一個(gè)方法后铣除,這個(gè)方法就會(huì)被緩存到cache列表中谚咬,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒有尚粘,才去methodLists中查找方法择卦。這樣,對(duì)于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率秉继。

4.version:我們可以使用這個(gè)字段來提供類的版本信息祈噪。這對(duì)于對(duì)象的序列化非常有用,它可是讓我們識(shí)別出不同類定義版本中實(shí)例變量布局的改變尚辑。

元類 (metaclass)

元類 (metaclass) 也是一個(gè)對(duì)象辑鲤,那么元類的 isa 指針又指向哪里呢?為了設(shè)計(jì)上的完整杠茬,所有的元類的 isa 指針都會(huì)指向一個(gè)根元類 (root metaclass)月褥。根元類 (root metaclass) 本身的 isa 指針指向自己,這樣就行成了一個(gè)閉環(huán)瓢喉。

我們?cè)賮砜纯蠢^承關(guān)系宁赤,由于類方法的定義是保存在元類 (metaclass) 中,而方法調(diào)用的規(guī)則是灯荧,如果該類沒有一個(gè)方法的實(shí)現(xiàn),則向它的父類繼續(xù)查找盐杂。所以逗载,為了保證父類的類方法可以在子類中可以被調(diào)用,所以子類的元類會(huì)繼承父類的元類链烈,換而言之厉斟,類對(duì)象和元類對(duì)象有著同樣的繼承關(guān)系。如下圖:


class-diagram.jpg

圖中的Root Class 是指 NSObject强衡,我們可以從圖中看出:

1.NSObject 類包括它的對(duì)象實(shí)例方法擦秽。
2.NSObject 的元類包括它的類方法,例如 alloc 方法漩勤。
3.NSObject 的元類繼承自 NSObject 類感挥。
4.一個(gè) NSObject 的類中的方法同時(shí)也會(huì)被 NSObject 的子類在查找方法時(shí)找到

為了驗(yàn)證上面說的內(nèi)容,我做了以下幾個(gè)測(cè)試越败,在這之前需要先了解幾個(gè)函數(shù)具體的作用與實(shí)現(xiàn)方式触幼。

+class 與 objc_getClass()

1.調(diào)用+class方法是無法獲取meta-class,它只是返回類本身究飞。

2.對(duì)類對(duì)象調(diào)用objc_getClass()可以獲取它的meta-class置谦。

-isKindOfClass和isMemberOfClass
+ (BOOL) isKindOfClass:(Class)class
{
   Class r0 = object_getClass(self);
   while (1) {
       if (r0 == 0) {
           return 0;
       }else{
           if (r0 != class) {
               r0 = [r0 superclass];
           }else{
               return 1;
           }
       }
   }
}

+ (BOOL)isMemberOfClass:(Class)class
{
   return isa == (Class)aClass;
}

它們的內(nèi)部實(shí)現(xiàn)大概是這個(gè)樣子∫诟担可以看出:
isKindOfClass:確定一個(gè)對(duì)象是否是一個(gè)類的成員,或者是派生自該類的成員.
isMemberOfClass:確定一個(gè)對(duì)象是否是當(dāng)前類的成員

驗(yàn)證例子1
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

輸出結(jié)果:YES/NO/NO/NO媒峡。
1.[NSObject class]獲取的NSObject類對(duì)象 在函數(shù)體內(nèi)獲取的是它的元類,而NSObject的元類的父類就是NSObject本身葵擎,所以第一個(gè)是YES谅阿。
2.因?yàn)槭莍sMemberOfClass,所以元類不等于自己,返回NO
3.同上奔穿,獲得的是元類镜沽,這里元類的父類與類對(duì)象不相等。
4.同上贱田。

類的成員變量

如果把類的實(shí)例看成一個(gè) C 語言的結(jié)構(gòu)體(struct)缅茉,上面說的 isa 指針就是這個(gè)結(jié)構(gòu)體的第一個(gè)成員變量,而類的其它成員變量依次排列在結(jié)構(gòu)體中男摧。


class-member.jpg

驗(yàn)證例子2

@interface NSObject (test)
+ (void)foo;
- (void)foo
@end
@implementation NSObject (test)
- (void)foo {
    NSLog(@"test result");
}
@end
// 測(cè)試代碼
[NSObject foo];
[[NSObject new] foo];

測(cè)試結(jié)果:都正常的運(yùn)行了-foo方法蔬墩。
原因是NSObject的類方法保存在根元類中,根元類沒有這個(gè)方法便向它的父類尋找耗拓,根元類的父類指向NSObject本身拇颅,而成員方法是保存在類中的,所以找到了這個(gè)方法乔询。

可變與不可變

因?yàn)閷?duì)象在內(nèi)存中的排布可以看成一個(gè)結(jié)構(gòu)體樟插,該結(jié)構(gòu)體的大小并不能動(dòng)態(tài)變化。所以無法在運(yùn)行時(shí)動(dòng)態(tài)給對(duì)象增加成員變量竿刁。
相對(duì)的黄锤,對(duì)象的方法定義都保存在類的可變區(qū)域中。我們可以看到方法的定義列表是一個(gè)名為 methodLists的指針的指針(如下圖所示)食拜。通過修改該指針指向的指針的值鸵熟,就可以實(shí)現(xiàn)動(dòng)態(tài)地為某一個(gè)類增加成員方法。

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists  <---              OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

Category

Category是表示一個(gè)指向分類的結(jié)構(gòu)體的指針负甸,其定義如下:

typedef struct objc_category *Category
struct objc_category{
     char *category_name                         OBJC2_UNAVAILABLE; // 分類名
     char *class_name                            OBJC2_UNAVAILABLE;  // 分類所屬的類名
     struct objc_method_list *instance_methods   OBJC2_UNAVAILABLE;  // 實(shí)例方法列表
     struct objc_method_list *class_methods      OBJC2_UNAVAILABLE; // 類方法列表
     struct objc_protocol_list *protocols        OBJC2_UNAVAILABLE; // 分類所實(shí)現(xiàn)的協(xié)議列表
}

這個(gè)結(jié)構(gòu)體主要包含了分類定義的實(shí)例方法與類方法流强,其中instance_methods列表是objc_class中方法列表的一個(gè)子集,而class_methods列表是元類方法列表的一個(gè)子集呻待。
可發(fā)現(xiàn)打月,類別中沒有ivar成員變量指針,也就意味著:類別中不能夠添加實(shí)例變量和屬性

小結(jié):
1.實(shí)例對(duì)象是類的對(duì)象蚕捉,類對(duì)象是元類的對(duì)象僵控。
2.類對(duì)象和元類對(duì)象有著同樣的繼承關(guān)系
3.成員方法保存在類中,類方法保存在元類中鱼冀。

  1. Category包含對(duì)象方法列表和元類方法列表报破,但沒有ivar成員變量指針。所以只能添加方法不能添加變量和屬性千绪。
    5.調(diào)用類方法時(shí)充易,是先查找元類本身是否有該方法,如果沒有荸型,則元類向它的父類查找盹靴,而不是類對(duì)象的父類。

相關(guān)文章

神經(jīng)病院objc runtime入院考試
Objective-C對(duì)象模型及應(yīng)用
Objective-C Runtime 運(yùn)行時(shí)之一:類與對(duì)象
初識(shí) Objective-C Runtime
Runtime之類與對(duì)象總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市稿静,隨后出現(xiàn)的幾起案子梭冠,更是在濱河造成了極大的恐慌,老刑警劉巖改备,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件控漠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡悬钳,警方通過查閱死者的電腦和手機(jī)盐捷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來默勾,“玉大人碉渡,你說我怎么就攤上這事∧赴” “怎么了滞诺?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)环疼。 經(jīng)常有香客問我习霹,道長(zhǎng),這世上最難降的妖魔是什么秦爆? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任序愚,我火速辦了婚禮憔披,結(jié)果婚禮上等限,老公的妹妹穿的比我還像新娘。我一直安慰自己芬膝,他們只是感情好望门,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锰霜,像睡著了一般筹误。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上癣缅,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天厨剪,我揣著相機(jī)與錄音,去河邊找鬼友存。 笑死祷膳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屡立。 我是一名探鬼主播直晨,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了勇皇?” 一聲冷哼從身側(cè)響起罩句,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎敛摘,沒想到半個(gè)月后门烂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡着撩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年诅福,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拖叙。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氓润,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出薯鳍,到底是詐尸還是另有隱情咖气,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布挖滤,位于F島的核電站崩溪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏斩松。R本人自食惡果不足惜伶唯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惧盹。 院中可真熱鬧乳幸,春花似錦、人聲如沸钧椰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫡霞。三九已至瓶埋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诊沪,已是汗流浹背养筒。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留端姚,地道東北人晕粪。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寄锐,于是被迫代替她去往敵國(guó)和親兵多。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尖啡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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