Runtime源碼閱讀分享之對象的本質(zhì)徒扶,了解isa

image

引言

我們都知道,Runtime 是 Objective-C 這門動態(tài)語言的核心根穷,只有理解了它姜骡,我們才能夠更好的理解 Objective-C 到底是如何工作的导坟,在編程時,也會更加得心應(yīng)手圈澈。由于時間和精力有限惫周,此次我主要想從以下幾方面來進行 Runtime 源碼的閱讀,日后將會逐步完善士败。由于總體篇幅較長闯两,所以我將會每一部分拆分成一篇文章來具體分析。

目錄

一谅将、對象的本質(zhì)漾狼,了解 isa

二、對象的生命周期

三饥臂、對象的引用計數(shù)

四逊躁、對象的擴展方法

五、Runtime 的應(yīng)用

對象的本質(zhì)隅熙,了解 isa

首先來看一看對象和類的定義

/// Represents an instance of a class.
struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
};


// 其內(nèi)部有一個指向 Class 的指針稽煤,而 Class 是什么呢

// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

// 再來看一下  objc_class 的定義,需要注意的是,objc_class 也是繼承自 objc_object囚戚, 由于 objc_object 中已經(jīng)定義了一個 isa 指針酵熙,由于結(jié)構(gòu)體中所有的成員都是 public 的,所以 objc_class 也就擁有了 isa 并且也擁有訪問 isa 的權(quán)限驰坊。
struct objc_class : objc_object {
    // Class ISA;   // 此時匾二,類中的 isa 指針指向的是 metaClass 元類
    Class superclass;  // 父類
    cache_t cache;             // formerly cache pointer and vtable 類的方法緩存,因為 Runtime 時會把第一次遇到的方法緩存到方法緩存中拳芙,此后將直接從緩存中讀取方法察藐,極大提高了效率
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
  //  class_data_bits_t <==> class_rw_t + 一個 rr/alloc 位
};

所以到這里,我們也就理解到了舟扎,實際上分飞,類也是一個對象

  • 我們需要知道的是,在 Objective-C 中睹限,對象的方法并沒有存儲于對象的結(jié)構(gòu)體中(如果每一個對象都保存了自己能執(zhí)行的方法譬猫,那么對內(nèi)存的占用有極大的影響)。在調(diào)用實例方法時邦泄,而是通過 isa 指針來尋找相應(yīng)的類删窒,通過 class_data_bits_t 來尋找類中的方法。具體是如何尋找的顺囊,我們看??

    // 首先 class_data_bits_t 中有一個 bits 位
    struct class_data_bits_t {
    
        // Values are the FAST_ flags above.
        uintptr_t bits;
     public:
    
      // 這里返回的數(shù)據(jù)是 class_rw_t* 指針類型的數(shù)據(jù),在這個方法中我們可以看出蕉拢,將 bits 與 FAST_DATA_MASK 進行位運算特碳,只取其中的 [3, 47] 位轉(zhuǎn)換成 class_rw_t * 返回诚亚。
        class_rw_t* data() {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
    }
    
    // data() 返回的是一個 class_rw_t* 指針, class_rw_t 又是什么午乓?
    
    // 類中的屬性站宗、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中:
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro;
    
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    
    #if SUPPORT_INDEXED_ISA
        uint32_t index;
    #endif
    };
    // 由此我們可以看出, class_rw_t 中包含了一些關(guān)于類的信息益愈,比如 flag梢灭, 版本號, 方法數(shù)組蒸其, 屬性數(shù)組等敏释。而其中又有一個指向 class_ro_t 的指針, class_ro_t 又是什么摸袁?
    // 原來钥顽,class_ro_t 中存儲了當前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議靠汁。
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    }; 
    
  • 所以由此蜂大,我們知道 class_ro_t 保存的是在編譯期時就已經(jīng)確定的方法,所以當在編譯期時蝶怔, class_data_bits_t 將直接指向 class_ro_t 奶浦,而后在 Runtime 時,將會調(diào)用 class_data_bits_t 的 data() 直接將結(jié)果從 class_rw_t 轉(zhuǎn)化成 class_ro_t 指針踢星, 然后再初始化一個 class_rw_t 指針澳叉,此時它中的數(shù)據(jù)都為空,然后再設(shè)置它的 ro 變量和 flag斩狱, 最后再為其設(shè)置正確的 data

    /***********************************************************************
    * realizeClass
    * Performs first-time initialization on class cls, 
    * including allocating its read-write data.
    * Returns the real class structure for the class. 
    * Locking: runtimeLock must be write-locked by the caller
    **********************************************************************/
    static Class realizeClass(Class cls)
    {
        runtimeLock.assertWriting();
    
        const class_ro_t *ro;
        class_rw_t *rw;
        Class supercls;
        Class metacls;
        bool isMeta;
    
        if (!cls) return nil;
        if (cls->isRealized()) return cls;
        assert(cls == remapClass(cls));
    
        // fixme verify class is not in an un-dlopened part of the shared cache?
    
      // 強制轉(zhuǎn)化為 ro
        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro;
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // Normal class. Allocate writeable class data.
          // 這時候首先給 rw 分配內(nèi)存空間并且初始化為 0
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
          // 使 rw 指向 ro
            rw->ro = ro;
          // 設(shè)置 rw 的 flag 為 正在初始化和已經(jīng)初始化
    /**
          // class is realized - must never be set by compiler
              #define RO_REALIZED           (1<<31)
    
          // Values for class_rw_t->flags
          // These are not emitted by the compiler and are never used in class_ro_t. 
          // Their presence should be considered in future ABI versions.
          // class_t->data is class_rw_t, not class_ro_t
              #define RW_REALIZED           (1<<31)
    */
            rw->flags = RW_REALIZED|RW_REALIZING;
            cls->setData(rw);
        }
    
      // 判斷是否為 metaClass  RO_META (1 << 0)
        isMeta = ro->flags & RO_META;
    
        rw->version = isMeta ? 7 : 0;  // old runtime went up to 6
        // Choose an index for this class.
      // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
      cls->chooseClassArrayIndex();
    
      if (PrintConnecting) {
          _objc_inform("CLASS: realizing class '%s'%s %p %p #%u", 
                       cls->nameForLogging(), isMeta ? " (meta)" : "", 
                       (void*)cls, ro, cls->classArrayIndex());
      }
    
      // Realize superclass and metaclass, if they aren't already.
      // This needs to be done after RW_REALIZED is set above, for root classes.
      // This needs to be done after class index is chosen, for root metaclasses.
      supercls = realizeClass(remapClass(cls->superclass));
      metacls = realizeClass(remapClass(cls->ISA()));
      #if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa for some classes and/or platforms.
      // Set instancesRequireRawIsa.
      bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
      bool rawIsaIsInherited = false;
      static bool hackedDispatch = false;
    
      if (DisableNonpointerIsa) {
          // Non-pointer isa disabled by environment or app SDK version
          instancesRequireRawIsa = true;
      }
      else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&  
               0 == strcmp(ro->name, "OS_object")) 
      {
          // hack for libdispatch et al - isa also acts as vtable pointer
          hackedDispatch = true;
          instancesRequireRawIsa = true;
      }
      else if (supercls  &&  supercls->superclass  &&  
               supercls->instancesRequireRawIsa()) 
      {
          // This is also propagated by addSubclass() 
          // but nonpointer isa setup needs it earlier.
          // Special case: instancesRequireRawIsa does not propagate 
          // from root class to root metaclass
          instancesRequireRawIsa = true;
          rawIsaIsInherited = true;
      }
      
      if (instancesRequireRawIsa) {
          cls->setInstancesRequireRawIsa(rawIsaIsInherited);
      }
        // SUPPORT_NONPOINTER_ISA
      #endif
        
        // Update superclass and metaclass in case of remapping
      cls->superclass = supercls;
      cls->initClassIsa(metacls);
    
      // Reconcile instance variable offsets / layout.
      // This may reallocate class_ro_t, updating our ro variable.
      if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
      // Set fastInstanceSize if it wasn't set already.
      cls->setInstanceSize(ro->instanceSize);
    
      // Copy some flags from ro to rw
      if (ro->flags & RO_HAS_CXX_STRUCTORS) {
          cls->setHasCxxDtor();
          if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
              cls->setHasCxxCtor();
          }
      }
    
      // Connect this class to its superclass's subclass lists
      if (supercls) {
          addSubclass(supercls, cls);
      } else {
          addRootClass(cls);
      }
    
      // Attach categories
    // 在這個方法中將 rw 的方法列表耳高,屬性列表,協(xié)議列表賦值
      methodizeClass(cls);
    
      return cls;
    };
    

此時所踊,經(jīng)過 Runtime 的作用之后泌枪,現(xiàn)在內(nèi)存中的關(guān)系是,類中的 data 指針指向 class_data_bits_t秕岛, class_data_bits_t 結(jié)構(gòu)體中的 data() 方法獲取到的是 class_rw_t 指針碌燕, class_rw_t 結(jié)構(gòu)體中的 ro 指針指向 class_ro_t。圖如下:

圖片來自一位大神的博客继薛,侵權(quán)刪
  • 但是問題來了修壕,類的方法是如何被查找和調(diào)用的呢?由于我們已經(jīng)知道了遏考,在 ObjC 中慈鸠,實際上類也是一個特殊的對象,查找類的方法實際上就和查找實例方法采用同樣的機制灌具,但是如何才能讓他們采用同樣的機制呢青团?這時譬巫,元類的作用就顯現(xiàn)了出來。

    • metaClass 保證了類中也有一個指向 Class 類型的指針督笆,保證了類和對象的一致性芦昔,保證了類查找方法的機制與對象查找方法的機制保持同步。
      • 當實例方法調(diào)用時娃肿,通過對象的 isa 在類中獲取方法的實現(xiàn)
      • 當類方法調(diào)用時咕缎,通過類的 isa 在元類中獲取方法的實現(xiàn)

現(xiàn)在,我們的重點終于到了料扰, isa 到底是什么凭豪?

  • 我們在 Runtime 的源碼中可以看到,在不同的處理器上,這個共同體所分配的內(nèi)存位數(shù)是不同的记罚。

    union isa_t 
    {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    
    #if SUPPORT_PACKED_ISA
    
        // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
        // nonpointer must be the LSB (fixme or get rid of it)
        // shiftcls must occupy the same bits that a real class pointer would
        // bits + RC_ONE is equivalent to extra_rc + 1
        // RC_HALF is the high bit of extra_rc (i.e. half of its range)
    
        // future expansion:
        // uintptr_t fast_rr : 1;     // no r/r overrides
        // uintptr_t lock : 2;        // lock for atomic property, @synch
        // uintptr_t extraBytes : 1;  // allocated with extra bytes
    
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
        struct {
            uintptr_t nonpointer        : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 19;
    #       define RC_ONE   (1ULL<<45)
    #       define RC_HALF  (1ULL<<18)
        };
    
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    #   define ISA_MAGIC_MASK  0x001f800000000001ULL
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
        struct {
            uintptr_t nonpointer        : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 8;
    #       define RC_ONE   (1ULL<<56)
    #       define RC_HALF  (1ULL<<7)
        };
    };
    

    x86_64_ 為例墅诡,

    圖片來自一位大神的博客,侵權(quán)刪

  • 更深一步桐智,從 isa 的初始化來看 isa 的結(jié)構(gòu)

    inline void 
    objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        initIsa(cls, true, hasCxxDtor);
    }
    
    inline void 
    objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
    { 
        if (!indexed) {
            isa.cls = cls;
        } else {
            isa.bits = ISA_MAGIC_VALUE;
            isa.has_cxx_dtor = hasCxxDtor;
            isa.shiftcls = (uintptr_t)cls >> 3;
        }
    }
    
    // 由于對象的 isa 初始化時傳入 indexed 為 true 末早,所以,可簡化為
    inline void 
    objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
    { 
      // 其中 ISA_MAGIC_VALUE 為 0x000001a000000001ULL
            isa.bits = ISA_MAGIC_VALUE;
            isa.has_cxx_dtor = hasCxxDtor;
            isa.shiftcls = (uintptr_t)cls >> 3;
    }
    
    • 此時说庭,當執(zhí)行完 isa.bits = ISA_MAGIC_VALUE; 后 isa 的結(jié)構(gòu)為 然磷,可以看到 ISA_MAGIC_VALUE 將 magic 和 indexed 都初始化了

      image
    • 接著 isa.has_cxx_dtor = hasCxxDtor; 這一位會設(shè)置 has_cxx_dtor 的值,如果是 1刊驴, 則表示當前對象是否有析構(gòu)器姿搜,如果沒有,就會快速釋放

    • 最后捆憎, isa.shiftcls = (uintptr_t)cls >> 3; 將當前對象對應(yīng)的類指針賦值給 shiftcls 這些位舅柜,之所以向右移三位,移三位的主要原因是用于將 Class 指針中無用的后三位清楚減小內(nèi)存的消耗躲惰,因為類的指針要按照字節(jié)(8 bits)對齊內(nèi)存致份,其指針后三位都是沒有意義的 0。賦值之后如下

![image](http://upload-images.jianshu.io/upload_images/3262069-f3a02c34c16975e6?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

至此础拨,也就證明了我們之前對于初始化 `isa` 時對 `initIsa` 方法的分析是正確的氮块。它設(shè)置了 `indexed`、`magic` 以及 `shiftcls`诡宗。

獲取 isa

  • 由于我們現(xiàn)在使用了結(jié)構(gòu)體 isa_t 來替代 Class 類型的指針滔蝉, 所以我們也就需要一個指針能夠返回 isa 所指的類,所以我們此時需要一個 ISA() 方法塔沃。

    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;
    #else
        return (Class)(isa.bits & ISA_MASK);
    #endif
    }
    
    // 簡化后如下
    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer());
      // 由此可以看到蝠引,實際上 ISA() 返回的是 isa.bits 與 0x0000000ffffffff8ULL 進行的按位與操作,確實可以返回當前的類
        return (Class)(isa.bits & ISA_MASK);
    }
    

總結(jié)

  • 至此,此次源碼分析的第一部分也就此結(jié)束立肘,如果您發(fā)現(xiàn)了什么問題和不足歡迎與我探討和指教边坤。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谅年,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肮韧,更是在濱河造成了極大的恐慌融蹂,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弄企,死亡現(xiàn)場離奇詭異超燃,居然都是意外死亡,警方通過查閱死者的電腦和手機拘领,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門意乓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人约素,你說我怎么就攤上這事届良。” “怎么了圣猎?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵士葫,是天一觀的道長。 經(jīng)常有香客問我送悔,道長慢显,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任欠啤,我火速辦了婚禮荚藻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘洁段。我一直安慰自己应狱,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布眉撵。 她就那樣靜靜地躺著侦香,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纽疟。 梳的紋絲不亂的頭發(fā)上罐韩,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音污朽,去河邊找鬼散吵。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的矾睦。 我是一名探鬼主播晦款,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枚冗!你這毒婦竟也來了缓溅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赁温,失蹤者是張志新(化名)和其女友劉穎坛怪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體股囊,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡袜匿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了稚疹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片居灯。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖内狗,靈堂內(nèi)的尸體忽然破棺而出怪嫌,到底是詐尸還是另有隱情,我是刑警寧澤其屏,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布喇勋,位于F島的核電站,受9級特大地震影響偎行,放射性物質(zhì)發(fā)生泄漏川背。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一蛤袒、第九天 我趴在偏房一處隱蔽的房頂上張望熄云。 院中可真熱鬧,春花似錦妙真、人聲如沸缴允。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽练般。三九已至,卻和暖如春锈候,著一層夾襖步出監(jiān)牢的瞬間薄料,已是汗流浹背携冤。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工羹蚣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笼踩。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像谷市,于是被迫代替她去往敵國和親蛔垢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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