Runtime源碼剖析---圖解對象奴曙、類與isa

Runtime源碼剖析---圖解對象别凹、類與isa

源碼面前,了無秘密

  • iOS開發(fā)的過程中洽糟,對象炉菲、類應該是我們接觸最的一個部分,本篇文章就以對象為主題坤溃,分一下對象和類在底層是如何實現(xiàn)的拍霜,讓你更深入了解iOS開發(fā)。
  • 從這篇博客開始我們就會進行Runtime源碼分析薪介,所以你需要準備一份最新的源代碼祠饺,源碼建議從Apple官方獲取
  • 本篇博客所用的是750.1版本的objc4源碼(目前最新版)

對象

objc_object定義

  • OC中每一個對象都是一個結(jié)構(gòu)體,結(jié)構(gòu)體中都包含一個isa的成員變量汁政,其位于成員變量的第一位

如何在源碼中找到它道偷?

  • 我們先在源碼中找到objc_object在哪,于是你打開全局搜索记劈,找到了這么一段
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
  • 于是你認為它里面就一個Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  • 然而勺鸦,請注意最上面的#if !OBJC_TYPES_DEFINED,點進去會發(fā)現(xiàn)該宏是1目木,說明根本不會走這個方法换途。
  • 然而真正的定義是在objc-private文件里
struct objc_object {
private:
    isa_t isa;
public:
        //此處省略方法
};
  • 我們不需要關(guān)心它的方法,我們來看看它的成員變量

isa_t

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;//----視線放在這------
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
  • isa_t的作用就是用來存儲類的信息
  • 關(guān)于isa_t這個結(jié)構(gòu)我們在下面會詳細剖析
  • 我們把視線放在Class cls這個變量,這個到底是什么呢军拟?
    • 它其實就我們口中的類剃执,下面我們來仔細看看類的內(nèi)部實現(xiàn)

objc_class定義

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    //省略方法
}  

成員變量

  • 第一個變量superclass:指向他的父類
  • 第二個變量cache:這里面存儲的方法緩存,這個知識點我會在下一篇文章中仔細剖析
  • 第三個變量bits:存儲對象的方法吻谋、屬性忠蝗、協(xié)議等信息现横,這個知識點我會在下一篇文章中仔細剖析

繼承關(guān)系

  • 從繼承關(guān)系我們就會發(fā)現(xiàn)漓拾,原來objc_class是繼承于objc_object,

    • 那就是說類其實也是一個對象
    • 還說明類實例化后也會包含isa這樣一個成員
    • 用一張圖來表示
    image
  • 這個時候就會有一個疑問?

    • 既然類繼承對象戒祠,它也有一個isa骇两,前面我們說了這個成員的作用是記錄類的信息的,那么我們類也擁有這個成員姜盈,那它也應該來記錄一些信息低千,那它記得的是什么信息呢?這個時候我們需要引進一個概念元類

    注意??:

    • 學習過程中馏颂,會發(fā)現(xiàn)很多人將isa稱之為isa指針,的確在32位機時代它就是一個指針示血,但是在現(xiàn)在64位機時代,它是一個結(jié)構(gòu)體救拉,同時他也包含了指針的作用难审。具體為什么,我在后面會為大家詳細解釋
    • 但是為方便講述亿絮,下面也開始使用isa指向xxx這種說法

元類

  • 元類的定義:元類是Class對象的類告喊,類的isa會指向其元類

  • 根類的定義:根類是所有對象的父類(除了特殊情況),它沒有父類派昧,一般情況下就是指NSObject

  • 為什么會定義元類這個類黔姜?

    方法的調(diào)用機制:

    • 因為在 Objective-C中,對象的方法并沒有存儲于對象的結(jié)構(gòu)體中(如果每一個對象都保存了自己能執(zhí)行的方法蒂萎,那么對內(nèi)存的占用有極大的影響)秆吵。

    • 實例方法被調(diào)用時,它要通過自己持有的 isa 來查找對應的類五慈,然后在這里的 class_data_bits_t 結(jié)構(gòu)體中查找對應方法的實現(xiàn)巡揍。同時,每一個 objc_class 也有一個指向自己的父類的指針 super_class 用來查找繼承的方法烈涮。

    • 既然類中存儲的是實例方法适揉,每個對象需要調(diào)用實例方法都來類里尋找即可,那么如果一個類需要調(diào)用類方法的時候聪轿,我們是如何查找并調(diào)用的呢爷肝?
      • 這個時候就需要引入元類來保證無論是類還是對象都能通過相同的機制查找方法的實現(xiàn)
  • 引入元類這個概念后,這樣就達到了使類方法和實例方法的調(diào)用機制相同的目的:

    • 實例方法調(diào)用時灯抛,通過對象的 isa 在類中獲取方法的實現(xiàn)
    • 類方法調(diào)用時金赦,通過類的 isa 在元類中獲取方法的實現(xiàn)
  • 下面這張圖介紹了對象、類與元類之間的關(guān)系

image
  • 注意??:
    • Root class根類对嚼,它是繼承關(guān)系的頂點夹抗,它不繼承于任何類
    • Root meta class根元類,它是isa指向的頂點纵竖,其isa直接指向自己,它繼承于根類

isa_t結(jié)構(gòu)剖析

結(jié)構(gòu)分析

  • 我們先再來看一遍他的結(jié)構(gòu)
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

//struct中的結(jié)構(gòu)
#   define ISA_BITFIELD                                                        
      uintptr_t nonpointer        : 1;                                         
      uintptr_t has_assoc         : 1;                                         
      uintptr_t has_cxx_dtor      : 1;                                         
      uintptr_t shiftcls          : 44; 
      uintptr_t magic             : 6;                                         
      uintptr_t weakly_referenced : 1;                                         
      uintptr_t deallocating      : 1;                                         
      uintptr_t has_sidetable_rc  : 1;                                         
      uintptr_t extra_rc          : 8

注意??:這是在 __x86_64__ 上的實現(xiàn)漠烧,對于 iPhone5s 等架構(gòu)為 __arm64__ 的設備上,具體結(jié)構(gòu)體的實現(xiàn)和位數(shù)可能有些差別靡砌,不過這些字段都是存在的已脓,由于源碼是在Mac OS運行的,所以我們就以__x86_64__ 為例進行講解

  • isa_t是一個union的結(jié)構(gòu)對象通殃,union類似于C++結(jié)構(gòu)體度液,其內(nèi)部可以定義成員變量和函數(shù)。在isa_t中定義了cls画舌、bits堕担、struct三部分。聯(lián)合體的大小取決的最大的那個成員變量曲聂,最大就是struct結(jié)構(gòu)體霹购,它占有64位,所以union的大小就是64位

cls對象

  • 在前面我已經(jīng)講過了它代表的是類句葵,如果有忘記的可以再回去看看厕鹃。

bits對象

  • 它其實是一個unsigned long類型
  • 它是用來獲取類指針,具體怎么操作下面我會詳解

struct

  • 下面對isa_t中的結(jié)構(gòu)體進行了位域聲明乍丈,地址從nonpointer起到extra_rc結(jié)束剂碴,從低到高進行排列。位域也是對結(jié)構(gòu)體內(nèi)存布局進行了一個聲明轻专,通過下面的結(jié)構(gòu)體成員變量可以直接操作某個地址忆矛。位域總共占8字節(jié),所有的位域加在一起正好是64位请垛。

    小提示:unionbits可以操作整個內(nèi)存區(qū)催训,而位域只能操作對應的位。

  • 下面我們用一張圖來展示strucr的位域

image
  • 下面我們看一下具體的存儲地址
image

isa_t初始化過程

  • 我們可以通過 isa 初始化的方法 initIsa 來初步了解這 64 位的 bits 的作用:
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool index, bool hasCxxDtor) 
{ 
    if (!indexed) {
        isa.cls = cls;
    } else {
        isa.bits = ISA_MAGIC_VALUE;
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}
  • 上來就看不懂宗收,index是個什么漫拭,為什么在這里傳的是true?在這里我給大家推薦一篇大神博客:Non-pointer isa

    • 大概的意思是在64位系統(tǒng)中混稽,為了降低內(nèi)存使用采驻,提升性能审胚,isa中有一部分字段用來存儲其他信息。這也解釋了上面isa_t的那部分結(jié)構(gòu)體礼旅。
  • 由于在 initInstanceIsa 方法中傳入了index = true膳叨,初始化就分為三步

indexedmagic

  • 初始化第一步
isa.bits = ISA_MAGIC_VALUE;
  • 我們來看看ISA_MAGIC_VALUE的定義
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
二進制表示:11101100000000000000000000000000000000000000000000001
  • 我們轉(zhuǎn)換成二進制數(shù)據(jù),然后看一下哪些屬性對應的位域被這行代碼初始化了(標記為紅色)
image
  • 從圖中了解到痘系,在使用 ISA_MAGIC_VALUE 設置 isa_t 結(jié)構(gòu)體之后菲嘴,實際上只是設置了 indexed 以及 magic 這兩部分的值。

    • 其中 indexed 表示 isa_t 的類型

      • 0 表示 raw isa汰翠,也就是沒有結(jié)構(gòu)體的部分龄坪,訪問對象的 isa 會直接返回一個指向 cls 的指針,也就是在 iPhone 遷移到 64 位系統(tǒng)之前時 isa 的類型奴璃。
      union isa_t {
          isa_t() { }
          isa_t(uintptr_t value) : bits(value) { }
      
          Class cls;
          uintptr_t bits;
      };
      
      • 1 表示當前 isa 不是指針悉默,但是其中也有 cls 的信息城豁,只是其中關(guān)于類的指針都是保存在 shiftcls 中苟穆。
      union isa_t {
          isa_t() { }
          isa_t(uintptr_t value) : bits(value) { }
      
          Class cls;
          uintptr_t bits;
      
          struct {
              uintptr_t indexed           : 1;
              uintptr_t has_assoc         : 1;
              uintptr_t has_cxx_dtor      : 1;
              uintptr_t shiftcls          : 44;
              uintptr_t magic             : 6;
              uintptr_t weakly_referenced : 1;
              uintptr_t deallocating      : 1;
              uintptr_t has_sidetable_rc  : 1;
              uintptr_t extra_rc          : 8;
          };
      };
      
    • magic 的值為 0x3b 用于調(diào)試器判斷當前對象是真的對象還是沒有初始化的空間

has_cxx_dtor

  • 初始化第二步
isa.has_cxx_dtor = hasCxxDtor;
  • has_cxx_dtor表示當前對象有 C++ 或者 ObjC 的析構(gòu)器(destructor),如果沒有析構(gòu)器就會快速釋放內(nèi)存唱星。
image

shiftcls

  • 初始化第三步
isa.shiftcls = (uintptr_t)cls >> 3;
  • shiftcls代表類真正的地址雳旅,將當前對象對應的類指針存入 isa 結(jié)構(gòu)體中了。

將當前地址右移三位的主要原因是用于將 Class 指針中無用的后三位清除減小內(nèi)存的消耗间聊,因為類的指針要按照字節(jié)(8 bits)對齊內(nèi)存攒盈,其指針后三位都是沒有意義的 0

  • 地址填進去后哎榴,位域變化如下
image
  • 其中紅色的為類指針型豁,這也就驗證了我們之前對于初始化 isa 時對 initIsa 方法的分析是正確的。它設置了 indexed尚蝌、magic 以及 shiftcls迎变。

其他位域

isa_t 中,我們還有一些沒有介紹的其它 bits飘言,在這個小結(jié)就簡單介紹下這些 bits 的作用

  • has_assoc

    • 對象含有或者曾經(jīng)含有關(guān)聯(lián)引用衣形,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存
  • weakly_referenced

    • 對象被指向或者曾經(jīng)指向一個 ARC 的弱變量,沒有弱引用的對象可以更快釋放
  • deallocating

    • 對象正在釋放內(nèi)存
  • has_sidetable_rc

    • 對象的引用計數(shù)太大了姿鸿,存不下
  • extra_rc

    • 對象的引用計數(shù)超過 1谆吴,會存在這個這個里面,如果引用計數(shù)為 10苛预,extra_rc 的值就為 9

isa的應用

獲取cls地址

  • 由于現(xiàn)在isa不在只存放地址了句狼,還多了很多附加內(nèi)容,因此需要一個專門的方法獲取shiftcls中的內(nèi)容
  • 我在前面提到了bits的用法热某,現(xiàn)在就是它的用武之地腻菇,它通過與ISA_MASK按&操作突诬,就能從64位域中,獲得shiftcls的值芜繁,也就是類的地址
#define ISA_MASK 0x00007ffffffffff8ULL
inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

class方法

  • 進入源碼以后旺隙,可以查看很多內(nèi)容的源碼
+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
  • class既是類方法又是實例方法,類方法直接返回自身骏令,實例方法返回的就是isa中的內(nèi)容

isMemberOfClass&&isKindOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • 這也沒啥好解釋的了蔬捷,結(jié)合class的內(nèi)容應該很好理解了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市榔袋,隨后出現(xiàn)的幾起案子周拐,更是在濱河造成了極大的恐慌,老刑警劉巖凰兑,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妥粟,死亡現(xiàn)場離奇詭異,居然都是意外死亡吏够,警方通過查閱死者的電腦和手機勾给,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锅知,“玉大人播急,你說我怎么就攤上這事∈鄱茫” “怎么了桩警?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昌妹。 經(jīng)常有香客問我捶枢,道長,這世上最難降的妖魔是什么飞崖? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任烂叔,我火速辦了婚禮,結(jié)果婚禮上蚜厉,老公的妹妹穿的比我還像新娘长已。我一直安慰自己,他們只是感情好昼牛,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布术瓮。 她就那樣靜靜地躺著,像睡著了一般贰健。 火紅的嫁衣襯著肌膚如雪胞四。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天伶椿,我揣著相機與錄音辜伟,去河邊找鬼氓侧。 笑死,一個胖子當著我的面吹牛导狡,可吹牛的內(nèi)容都是我干的约巷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼旱捧,長吁一口氣:“原來是場噩夢啊……” “哼独郎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起枚赡,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤氓癌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贫橙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贪婉,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年卢肃,在試婚紗的時候發(fā)現(xiàn)自己被綠了疲迂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡践剂,死狀恐怖鬼譬,靈堂內(nèi)的尸體忽然破棺而出娜膘,到底是詐尸還是另有隱情逊脯,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布竣贪,位于F島的核電站军洼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏演怎。R本人自食惡果不足惜匕争,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爷耀。 院中可真熱鬧甘桑,春花似錦、人聲如沸歹叮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咆耿。三九已至德谅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萨螺,已是汗流浹背窄做。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工愧驱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人椭盏。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓组砚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掏颊。 傳聞我的和親對象是個殘疾皇子惫确,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

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