Runtime學(xué)習(xí)-類的數(shù)據(jù)結(jié)構(gòu)

1、Runtime的定義

將源代碼轉(zhuǎn)換為可執(zhí)行的程序,通常要經(jīng)過(guò)三個(gè)步驟:編譯、鏈接、運(yùn)行。

Objective-C 語(yǔ)言 是一門(mén)動(dòng)態(tài)語(yǔ)言,在編譯階段并不知道變量的具體數(shù)據(jù)類型,也不知道所真正調(diào)用的哪個(gè)函數(shù)。只有在運(yùn)行時(shí)間才檢查變量的數(shù)據(jù)類型,同時(shí)在運(yùn)行時(shí)才會(huì)根據(jù)函數(shù)名查找要調(diào)用的具體函數(shù)。這樣在程序沒(méi)運(yùn)行的時(shí)候,我們并不知道調(diào)用一個(gè)方法具體會(huì)發(fā)生什么。

Objective-C 語(yǔ)言把一些決定性的工作從編譯階段、鏈接階段推遲到運(yùn)行時(shí)階段,使得 Objective-C 變得更加靈活。這也就說(shuō)只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來(lái)執(zhí)行編譯后的代碼。這就是 Objective-C Runtime 系統(tǒng)存在的意義。

Runtime 實(shí)際上是一個(gè)庫(kù),這個(gè)庫(kù)使我們可以在程序運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建對(duì)象、檢查對(duì)象,修改類和對(duì)象的方法。

Runtime 基本是用 C 和匯編寫(xiě)的,可見(jiàn)蘋(píng)果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋(píng)果維護(hù)的開(kāi)源代碼。

2、與Runtime進(jìn)行交互

OC支持從三種不同的層級(jí)上與 Runtime 系統(tǒng)進(jìn)行交互,分別是通過(guò) Objective-C 源代碼NSObject類定義的方法,Runtime 函數(shù)的直接調(diào)用。

Objective-C 源代碼

使用OC在編寫(xiě)代碼的時(shí)候,基本都是使用類、屬性、方法調(diào)用、協(xié)議、分類。但是在這些的背后,都是由runtime來(lái)支持的。我們平常所熟知的各種類型,背后都有runtime對(duì)應(yīng)的C語(yǔ)言結(jié)構(gòu)體,及C和匯編實(shí)現(xiàn)。比如類就被編譯成objc_class的結(jié)構(gòu)體,方法被編譯成method_t的結(jié)構(gòu)體等等。

NSObject 的方法

Cocoa 中大多數(shù)類都繼承于 NSObject 類,也就自然繼承了它的方法。作為大部分Objective-C類繼承體系的根類的NSObject,其本身就具有了一些非常具有運(yùn)行時(shí)動(dòng)態(tài)特性的方法,使用這些方法也算是一種與Runtme的交互方式,舉例如下:

//返回對(duì)象的類;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
//檢查對(duì)象對(duì)應(yīng)的類,與指定的類aClass及其父類進(jìn)行判斷。只要有相同的類,就判斷為true
- (BOOL)isKindOfClass:(Class)aClass;
//檢查對(duì)象所在的類,與指定的類aClass,如果一致就返回true
- (BOOL)isMemberOfClass:(Class)aClass;
//用來(lái)判斷對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol; 
//用來(lái)判斷對(duì)象能否響應(yīng)指定的消息;
- (BOOL)respondsToSelector:(SEL)aSelector; 
2、Runtime 的函數(shù)

Runtime 系統(tǒng)是一個(gè)由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成,具有公共接口的動(dòng)態(tài)共享庫(kù)。頭文件存放于/usr/include/objc目錄下。許多函數(shù)允許你用純C代碼來(lái)重復(fù)實(shí)現(xiàn) Objc 中同樣的功能。在文件中引入Runtime的頭文件#include <objc/runtime.h>,就可以使用Runtime的方法,并且適合OC程序是一樣的效果。舉例如下:

   Man * m = [[Man alloc] init];
  //OC語(yǔ)句
  Class mClass = [m class];
  //runtime函數(shù)
  Class objClass = object_getClass(m);
  NSLog(@"mClass = %@, objClass = %@",mClass,objClass);
  //打印結(jié)果如下
  //2022-05-31 09:40:14.506089+0800 schemeUse[1860:59157] mClass = Man, objClass = Man

3、Runtime中類的數(shù)據(jù)結(jié)構(gòu)

Objc 2.0之前的版本
objc 2.0.png
Objc 2.0之后的版本
after-objc_2.0.png

由OBJC 2.0后的版本的代碼可以看出:

  1. Class類是一個(gè)objc_class *指針的別名
  2. objc_class是繼承objc_object的結(jié)構(gòu)體
  3. 由于objc_object有一個(gè)實(shí)例變量isa,并且objc_objectobjc_class的superClass,則在objc_class必然是有isa實(shí)例變量。
  4. 在Runtime系統(tǒng)中,對(duì)象被定義為一個(gè)objc_object類型的結(jié)構(gòu)體,所以Class也被看作是一類特殊的對(duì)象。
  5. id是一種objc_object *的指針的別名
4、了解isa

從Objc 2.0以后的源代碼可以看出,所有的類都是繼承于objc_object,而objc_object中只有一個(gè)變量,即 isa_t類型的isa。也就是說(shuō)Objective-C 對(duì)象都是 C 語(yǔ)言結(jié)構(gòu)體,所有的對(duì)象都包含一個(gè)類型為 isa 的指針。所有與類相關(guān)的信息,都是在isa中進(jìn)行存儲(chǔ),比如說(shuō):是否指針優(yōu)化、是否有關(guān)聯(lián)對(duì)象、是否被弱引用等。

那么isa的作用是什么呢?

  1. 方法查詢機(jī)制。在Object-C中,對(duì)象的方法并不存儲(chǔ)在各個(gè)對(duì)象的內(nèi)存中(如果每一個(gè)對(duì)象都保存了自己能執(zhí)行的方法,那么對(duì)內(nèi)存的占用有極大的影響),而是保存在類的結(jié)構(gòu)體中(class_ro_t)。當(dāng)實(shí)例對(duì)象調(diào)用方法的時(shí)候,該對(duì)象要通過(guò)自己的isa找到對(duì)應(yīng)的類,在類的方法列表(class_ro_t)中查找方法的實(shí)現(xiàn)。如果類的方法列表中不存在,就是用類的superClass去父類的方法列表繼續(xù)查找。
    但是如果是類方法的調(diào)用,就會(huì)有不同。Objective-C為了在調(diào)用方法的時(shí)候,使得類方法和對(duì)象方法保持一致的方法查找機(jī)制,就引入了 元類的概念。每一個(gè)類的isa都指向自己的元類,在類中定義的類方法,存儲(chǔ)在元類中。在類方法的調(diào)用過(guò)程中,是根據(jù)isa查詢到自己的元類,在元類的方法列表中,查找類方法實(shí)現(xiàn)。
    isa指向

    isa和superClass指向.png

  2. 表明是否有關(guān)聯(lián)對(duì)象(has_assoc)。

  3. 表明是否有弱應(yīng)用(weakly_referenced)。

  4. 對(duì)象的引用計(jì)數(shù)信息(extra_rc)。

  5. 存儲(chǔ)對(duì)象的類的地址信息(shiftcls)。

isa的數(shù)據(jù)結(jié)構(gòu):

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)
  };

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif


#if SUPPORT_INDEXED_ISA

# if  __ARM_ARCH_7K__ >= 2

#   define ISA_INDEX_IS_NPI      1
#   define ISA_INDEX_MASK        0x0001FFFC
#   define ISA_INDEX_SHIFT       2
#   define ISA_INDEX_BITS        15
#   define ISA_INDEX_COUNT       (1 << ISA_INDEX_BITS)
#   define ISA_INDEX_MAGIC_MASK  0x001E0001
#   define ISA_INDEX_MAGIC_VALUE 0x001C0001
  struct {
      uintptr_t nonpointer        : 1;
      uintptr_t has_assoc         : 1;
      uintptr_t indexcls          : 15;
      uintptr_t magic             : 4;
      uintptr_t has_cxx_dtor      : 1;
      uintptr_t weakly_referenced : 1;
      uintptr_t deallocating      : 1;
      uintptr_t has_sidetable_rc  : 1;
      uintptr_t extra_rc          : 7;
#       define RC_ONE   (1ULL<<25)
#       define RC_HALF  (1ULL<<6)
  };

# else
#   error unknown architecture for indexed isa
# endif

// SUPPORT_INDEXED_ISA
#endif

};

可以看出isa_t 主要是有Class cls 和不同系統(tǒng)下的結(jié)構(gòu)體組成。在不同的系統(tǒng)下 ,結(jié)構(gòu)體的參數(shù)是一樣的,只是空間分配有所不同。這樣的話。這里以"__arm64__“進(jìn)行展示:

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

  Class cls;
  uintptr_t bits;
  
  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;
  };
};

isa_t是一個(gè)共用體union。
union:在一個(gè)“聯(lián)合”內(nèi)可以定義多種不同的數(shù)據(jù)類型, 一個(gè)該“聯(lián)合”類型的變量中,允許裝入該“聯(lián)合”所定義的任何一種數(shù)據(jù),這些數(shù)據(jù)共享同一段內(nèi)存,以達(dá)到節(jié)省空間的目的。這種“union”只能存放一種數(shù)據(jù),在isa_t中cls和結(jié)構(gòu)體只能使用一個(gè)變量。

isa字段.png

示意圖


arm64.png

nonpointer

自2013年蘋(píng)果推出iphone5s之后,iOS的尋址空間擴(kuò)大到了64位。我們可以用63位來(lái)表示一個(gè)數(shù)字(一位做符號(hào)位)。那么這個(gè)數(shù)字的范圍是2^63 ,很明顯我們一般不會(huì)用到這么大的數(shù)字,那么在我們定義一個(gè)數(shù)字時(shí)NSNumber *num = @100,實(shí)際上內(nèi)存中浪費(fèi)了很多的內(nèi)存空間。

當(dāng)然蘋(píng)果肯定也認(rèn)識(shí)到了這個(gè)問(wèn)題,于是就引入了tagged pointer。tagged pointer是一種特殊的“指針”,其特殊在于,其實(shí)它存儲(chǔ)的并不是地址,而是真實(shí)的數(shù)據(jù)和一些附加的信息。
蘋(píng)果對(duì)于Tagged Pointer特點(diǎn)的介紹:


taggedpointer.jpg
  • Tagged Pointer專門(mén)用來(lái)存儲(chǔ)小的對(duì)象,例如NSNumber, NSDate, NSString。
  • Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已。所以,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free。
  • 在內(nèi)存讀取上有著3倍的效率,創(chuàng)建時(shí)比以前快106倍。


    tagged pointer.png

如上面tagged pointer所說(shuō),如果isa指針僅表示類型的話,對(duì)內(nèi)存顯然也是一個(gè)極大的浪費(fèi)。于是,就像tagged pointer一樣,對(duì)于isa指針,蘋(píng)果同樣進(jìn)行了優(yōu)化。isa指針表示的內(nèi)容變得更為豐富,除了表明對(duì)象屬于哪個(gè)類之外,還附加了引用計(jì)數(shù)extra_rc,是否有被weak引用標(biāo)志位weakly_referenced,是否有附加對(duì)象標(biāo)志位has_assoc等信息。

isa_t其實(shí)可以看做有兩個(gè)可能的取值,Class cls或struct。isa作為Class cls使用的時(shí)候,是我們一貫的認(rèn)知,存儲(chǔ)對(duì)應(yīng)的地址。這樣的話,內(nèi)存是有比較大的浪費(fèi)。所以絕大多數(shù)情況下,蘋(píng)果采用了優(yōu)化的isa策略,即,isa_t類型并不等同于Class cls, 而是struct。這在初始化isa的時(shí)候,表現(xiàn)很明顯。

有關(guān)聯(lián)對(duì)象標(biāo)志位has_assoc

  NSObject * obj = [[NSObject alloc] init];
  NSLog(@"isa_t = %p", *(void **)(__bridge void*)obj);
   //2022-06-07 09:50:26.535944+0800 schemeUse[2530:174823] isa_t = 0x21a1dfe7f289
  objc_setAssociatedObject(obj, "xxxx", self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  NSLog(@"isa_t-associate = %p", *(void **)(__bridge void*)obj);
  //2022-06-07 09:50:26.536051+0800 schemeUse[2530:174823] isa_t-associate = 0x21a1dfe7f28b

可以看出,添加關(guān)聯(lián)對(duì)象以后,isa是有變化的,全部轉(zhuǎn)成二進(jìn)制數(shù)據(jù)進(jìn)行對(duì)比:

添加關(guān)聯(lián)對(duì)象.jpg

對(duì)比上面“arm64”的示意圖,可以比較出來(lái),第二位數(shù)據(jù)變成了“1”,也就是 has_assoc位變化了。

被弱引用標(biāo)志位weakly_referenced

    NSObject * obj = [[NSObject alloc] init];
    NSLog(@"isa_t = %p", *(void **)(__bridge void*)obj);
    //2022-06-07 10:13:45.789576+0800 schemeUse[2647:180952] isa_t = 0x21a1dfe7f289
    __weak typeof(obj) weakObj = obj;
    NSLog(@"isa_t-weak = %p", *(void **)(__bridge void*)obj);
    //2022-06-07 10:13:45.789685+0800 schemeUse[2647:180952] isa_t-weak = 0x25a1dfe7f289

全部轉(zhuǎn)成二進(jìn)制數(shù)據(jù)進(jìn)行對(duì)比,可以看出第42位weakly_referenced位被置位1:

weak引用.jpg

isa中的引用計(jì)數(shù)extra_rc
代碼:

  NSObject * obj = [[NSObject alloc] init];
  NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
  NSLog(@"isa_t—1 = %p", *(void **)(__bridge void*)obj);
  //2022-06-08 10:56:14.495684+0800 schemeUse[6396:483268] refCount = 1
  //2022-06-08 10:56:14.495792+0800 schemeUse[6396:483268] isa_t-refrence-1 = 0x21a1dfe7f289

  NSObject * obj_refrence1 = obj;
  NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
  NSLog(@"isa_t-refrence-2 = %p", *(void **)(__bridge void*)obj);
  //2022-06-08 10:56:14.495831+0800 schemeUse[6396:483268] refCount = 2
  //2022-06-08 10:56:14.495864+0800 schemeUse[6396:483268] isa_t-refrence-2 = 0x41a1dfe7f289

  NSObject * obj_refrence2 = obj;
  NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
  NSLog(@"isa_t-refrence-3 = %p", *(void **)(__bridge void*)obj);
  //2022-06-08 10:56:14.495897+0800 schemeUse[6396:483268] refCount = 3
  //2022-06-08 10:56:14.495928+0800 schemeUse[6396:483268] isa_t-refrence-3 = 0x61a1dfe7f289

全部轉(zhuǎn)成二進(jìn)制數(shù)據(jù)進(jìn)行對(duì)比,可以看出extra_rc的19位數(shù)據(jù)的變化(需要指出的是,如果has_sidetable_rc的標(biāo)識(shí)位為1,那么也就意味著extra_rc的19位二進(jìn)制數(shù)據(jù)裝不下對(duì)象的引用計(jì)數(shù),也就保存到了sidetable一部分。這樣的話引用計(jì)數(shù)就是兩部分加起來(lái)。):

引用計(jì)數(shù).jpg

對(duì)象的實(shí)例化過(guò)程中對(duì)isa的創(chuàng)建過(guò)程

1、類調(diào)用alloc方法時(shí),runtime系統(tǒng)內(nèi)部會(huì)調(diào)用_objc_rootAlloc(self);
2、而 _objc_rootAlloc方法中只是調(diào)用了callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);;
3、在callAlloc方法中,分配對(duì)象的空間以及初始化isa(initInstanceIsa(cls, dtor)方法)。

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
       //其他代碼xxxxxxxx
       bool dtor = cls->hasCxxDtor();
       id obj = (id)calloc(1, cls->bits.fastInstanceSize());
       if (slowpath(!obj)) return callBadAllocHandler(cls);
       obj->initInstanceIsa(cls, dtor);
       return obj;
       //其他代碼zxxxxxxxxxx
}

4、initInstanceIsa 最終調(diào)用的是objc_object類的inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)方法

inline void  objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
     //nonpointer的值,在initInstanceIsa方法中默認(rèn)傳值為true。initIsa(cls, true, hasCxxDtor);
  if (!nonpointer) {
      //如果沒(méi)沒(méi)有使用isa優(yōu)化,使用isa結(jié)構(gòu)體中的cls,來(lái)接受對(duì)象對(duì)應(yīng)的類信息。
      isa.cls = cls;
  } else {
      //使用了isa優(yōu)化,創(chuàng)建一個(gè)新的isa,處理相關(guān)的信息,然后賦值給對(duì)象的isa
       //對(duì)象實(shí)例化的時(shí)候,需要處理的isa信息包括:has_cxx_dtor、shiftcls等
      isa_t newisa(0);
      /**其他代碼**/
      newisa.bits = ISA_MAGIC_VALUE;
      newisa.has_cxx_dtor = hasCxxDtor;
      newisa.shiftcls = (uintptr_t)cls >> 3;
      isa = newisa;
  }
}
對(duì)象的銷毀過(guò)程中,isa的作用

1、對(duì)象銷毀,會(huì)調(diào)用dealloc方法,該方法內(nèi)部調(diào)用的是_objc_rootDealloc(self);
2、_objc_rootDealloc方法內(nèi)部,調(diào)用起了obj->rootDealloc();
3、 rootDealloc方法中,主要是對(duì)isa的信息進(jìn)行判斷,檢查是否可以直接free對(duì)象

inline void
objc_object::rootDealloc()
{
  if (isTaggedPointer()) return;  // fixme necessary?

  if (fastpath(isa.nonpointer  &&     //對(duì)象的isa是有優(yōu)化的
               !isa.weakly_referenced  &&   //對(duì)象沒(méi)有被弱引用
               !isa.has_assoc  &&   // 不存在關(guān)聯(lián)對(duì)象
               !isa.has_cxx_dtor  &&   // 沒(méi)有 有C++的析構(gòu)函數(shù)
               !isa.has_sidetable_rc))  //在sidetable中不存在引用計(jì)數(shù)
  {
      assert(!sidetable_present());
      //直接free
      free(this);
  } 
  else {
      //需要去處理isa中相關(guān)的信息
      object_dispose((id)this);
  }
}

4、在 object_dispose方法中,調(diào)用objc_destructInstance(obj),清理isa中的相關(guān)信息,弱引用、關(guān)聯(lián)對(duì)象、c++析構(gòu)函數(shù)、引用計(jì)數(shù)等,清理完成以后,釋放對(duì)象。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();   //獲取isa中C++標(biāo)志位的信息
        bool assoc = obj->hasAssociatedObjects(); //獲取isa中關(guān)聯(lián)對(duì)象的信息

        // This order is important.
        if (cxx) object_cxxDestruct(obj); //C++標(biāo)志位有數(shù)據(jù),處理析構(gòu)
        if (assoc) _object_remove_assocations(obj);  // 清理關(guān)聯(lián)的對(duì)象
        obj->clearDeallocating(); //清理弱引用、sidetable的引用計(jì)數(shù)
    }

    return obj;
}
//清理弱引用、sidetable的引用計(jì)數(shù)
inline void  objc_object::clearDeallocating()
{
  if (slowpath(!isa.nonpointer)) {
      // Slow path for raw pointer isa.
      sidetable_clearDeallocating();
  }
  else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
      // Slow path for non-pointer isa with weak refs and/or side table data.
      clearDeallocating_slow();
  }

  assert(!sidetable_present());
}

5、類的結(jié)構(gòu)

在runtime系統(tǒng)中,objc_class是類的結(jié)構(gòu)體,objc_class繼承于objc_object

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 //存儲(chǔ)類的信息,成員變量,方法,協(xié)議等

  //可以使用data通過(guò)一些與或操作,獲取到成員變量、方法、協(xié)議等
    class_rw_t *data() {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    //初始化,以及獲取isa、類的信息的一些方法
    ///xxxxxxxx

};

可以看出,在objc_class中,有三個(gè)數(shù)據(jù):Class superclasscache_t cache、class_data_bits_t bits

Class superclass表明當(dāng)前類的父類,其本身也是一個(gè)Class類型。
  1. 根元類的父類,是NSObject
  2. NSObject的父類是nil。
cache_t cache cache的作用在于緩存方法, 方法調(diào)用的時(shí)候,可以更快速的找到方法的IMP

cache的數(shù)據(jù)結(jié)構(gòu)為:

//方法緩存的結(jié)構(gòu),共占用16個(gè)字節(jié)
struct cache_t {
private:
 //8個(gè)字節(jié),存放方法的空間首地址信息(low 48位)和mask信息(high 16位),
 explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
 
 union {
     struct {
         //_maybeMask is the buckets mask
         explicit_atomic<mask_t>    _maybeMask; //4個(gè)字節(jié),存儲(chǔ)的時(shí)候是newCapacity - 1
#if __LP64__
         uint16_t                   _flags;
#endif
         //緩存的方法數(shù)量
         uint16_t                   _occupied;
     };
     explicit_atomic<preopt_cache_t *> _originalPreoptCache;
 };

 //向緩存中添加方法
 void insert(SEL sel, IMP imp, id receiver);
 
 //取緩存方法列表
 struct bucket_t *buckets() const;
 
 //可以緩存方法的空間數(shù)量
 unsigned capacity() const;
 
 //已經(jīng)緩存的方法數(shù)量
 mask_t occupied() const;
 
 //增加已緩存方法的數(shù)量
 void incrementOccupied();
 
 //設(shè)置_bucketsAndMayBeMask
 void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);

 //開(kāi)辟緩存的空間
 void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);

 /**其他方法**/
};

cahce_t是一個(gè)結(jié)構(gòu)體,共計(jì)16個(gè)字節(jié)的大小。包含兩個(gè)成員,一個(gè)是_bucketsAndMaybeMask,一個(gè)是union聯(lián)合體,各占8個(gè)字節(jié)。cache_t中的方法主要圍繞這兩個(gè)成員的操作進(jìn)行的。

_bucketsAndMaybeMask:實(shí)際上是一塊內(nèi)存的首地址。方法緩存主要是以buckets(bucket_t)進(jìn)行存儲(chǔ),存儲(chǔ)內(nèi)容就是SELIMP。_bucketsAndMaybeMask通過(guò)和 mask的與操作,就能獲得緩存的方法列表。

  struct bucket_t *cache_t::buckets() const
  {
     uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
     return (bucket_t *)(addr & bucketsMask);
   }

方法的緩存過(guò)程:
方法緩存調(diào)用的是void insert(SEL sel, IMP imp, id receiver);方法。該方法要做的事情分為三塊:

  • 開(kāi)辟緩存空間
  • 緩存空間的擴(kuò)容
  • 方法插入緩存


void cache_t::insert(SEL sel, IMP imp, id receiver)
{
   
    //1.開(kāi)辟緩存空間
    // Use the cache as-is if until we exceed our expected fill ratio.
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        //如果緩存里面為空。先分配4個(gè)容量大小的空間。并調(diào)用reallocate去開(kāi)辟空間
        if (!capacity) capacity = INIT_CACHE_SIZE; // 1<<2
        //開(kāi)辟空間,并且給_bucketsAndMayBemask、_maybemask賦值
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    //2、緩存空間的擴(kuò)容
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }
#if CACHE_ALLOW_FULL_UTILIZATION
    else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
        // Allow 100% cache utilization for small buckets. Use it as-is.
    }
#endif
    else {
        
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }

    
    
    //3、方法插入緩存,方法插入緩存的代碼
    bucket_t *b = buckets();
    mask_t m = capacity - 1;//3
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();// _occupied++;
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

消息機(jī)制中,方法的緩存流程:
消息機(jī)制的緩存
class_data_bits_t bits: 保存類的成員變量、成員方法、協(xié)議、屬性

在之前isa的學(xué)習(xí)中,討論過(guò)對(duì)象的方法不存儲(chǔ)在各個(gè)對(duì)象的內(nèi)存中,而是保存在類的結(jié)構(gòu)體中。具體保存類結(jié)構(gòu)的什么位置呢?就是class_data_bits_t bits。使用該成員可以獲取到一個(gè)類的成員變量、成員方法、協(xié)議、屬性等信息。
class_data_bits_t數(shù)據(jù)結(jié)構(gòu)如下:

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;

public:
    //類的成員變量、成員方法、協(xié)議、屬性(包含分類創(chuàng)建的方法)
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }

    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
  //類的成員變量、成員方法、協(xié)議、屬性(只是在類中定義的,并不包含分類中的方法)
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }
    
    /*其他方法*/

};

這個(gè)結(jié)構(gòu)體是一個(gè)64bit的成員變量bits,先來(lái)看看這64bit分別存放的什么信息:

class_bits_t.png

可以看出數(shù)據(jù)的第4-48位,是存放一個(gè)指向class_rw_t結(jié)構(gòu)體的指針,該結(jié)構(gòu)體包含了該類的屬性,方法,協(xié)議等信息,是class_data_bits_t bits的核心。
下面就來(lái)說(shuō)說(shuō)核心部分:class_rw_tclass_ro_t
rw和ro.png

class_ro_t說(shuō)明:
1、該屬性是在編譯期就生成的,它存儲(chǔ)的內(nèi)容包含了當(dāng)前類的名稱,以及method_list_t, protocol_list_t, ivar_list_t, property_list_t 這些類的基本信息,在class_ro_t里面是沒(méi)有分類中定義的方法和協(xié)議的。

class_rw_t說(shuō)明:
1、是class_ro_t外面的一層,在運(yùn)行時(shí)生成的。在_objc_init方法中關(guān)于dyld的回調(diào)的map_images中,調(diào)用realizeClass方法,該方法最終將類和分類的所有方法、協(xié)議都插入到自己的方法列表、協(xié)議列表中。
2、它不包含成員變量列表,因?yàn)槌蓡T變量列表是在編譯期就確定好的,它只保存在class_ro_t中。
3、成員方法會(huì)保存在class_rw_t中。
4、class_rw_t中包含了一個(gè)指向class_ro_t的指針,可以方便的讀取類的成員變量信息。

在調(diào)用realizeClass方法之前,類的 class_data_bits_t是指向class_ro_t結(jié)構(gòu)體;調(diào)用realizeClass方法之后,class_data_bits_t是指向class_rw_t結(jié)構(gòu)體,把分類中的方法匯總到class_rw_t中。并且class_rw_t會(huì)有一個(gè)變量指向class_ro_t

class_bits_t的指向.png

細(xì)看兩個(gè)結(jié)構(gòu)體的成員變量會(huì)發(fā)現(xiàn)很多相同的地方,他們都存放著當(dāng)前類的屬性、實(shí)例變量、方法、協(xié)議等等。區(qū)別在于:class_ro_t存放的是編譯期間就確定的;而class_rw_t是在runtime時(shí)才確定,它會(huì)先將class_ro_t的內(nèi)容拷貝過(guò)去,然后再將當(dāng)前類的分類的這些屬性、方法等拷貝到其中。所以可以說(shuō)class_rw_t是class_ro_t的超集,當(dāng)然實(shí)際訪問(wèn)類的方法、屬性等也都是訪問(wèn)的class_rw_t中的內(nèi)容 。

6、類的總結(jié)圖示

類的大?。?/p>

runtime_image.jpg

類的結(jié)構(gòu)總結(jié)圖:


runtime_image.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容