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

一、類和元類的創(chuàng)建時機

這里先拋出結(jié)論:類和元類是在編譯期創(chuàng)建的,即在alloc之前,下面我們通過兩種方式來驗證:

1、LLDB打?。?/h5>
  • 斷點在int main()處:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 0x00007ffffffffff8
        Person *person = [Person alloc];
    }
    return 0;
}
  • 這個時候還沒走[Person alloc];,我們來打印一下:
(lldb) p/x Person.class
(Class) $0 = 0x0000000100001100 Person // 類

(lldb) x/4gx 0x0000000100001100
0x100001100: 0x001d8001000010d9 0x0000000100b35140
0x100001110: 0x00000001003d8280 0x0000000000000000
(lldb) p/x 0x001d8001000010d9 & 0x00007ffffffffff8
(long) $1 = 0x00000001000010d8
(lldb) po $1
Person // 元類

(lldb) x/4gx 0x00000001000010d8
0x1000010d8: 0x001d800100b350f1 0x0000000100b350f0
0x1000010e8: 0x0000000101f3faf0 0x0000000200000003
(lldb) p/x 0x001d800100b350f1 & 0x00007ffffffffff8
(long) $2 = 0x0000000100b350f0
(lldb) po $2
NSObject  // 根元類

(lldb) x/4gx 0x0000000100b350f0
0x100b350f0: 0x001d800100b350f1 0x0000000100b35140
0x100b35100: 0x0000000101f400c0 0x0000000400000007
(lldb) p/x 0x001d800100b350f1 & 0x00007ffffffffff8
(long) $3 = 0x0000000100b350f0
(lldb) po $3
NSObject  // 根根元類
2、Macho工具:
  • command + B進行編譯,然后將可執(zhí)行文件拖入Macho中查看。
    (沒圖,我的Macho打不開了)

二、類結(jié)構(gòu)分析

1、objc_class源碼跟蹤

以下源碼來自objc-runtime-new.h文件中,從源碼我們可以看到類的本質(zhì)是結(jié)構(gòu)體,繼承自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

    class_rw_t *data() { 
        return bits.data();
    }
  // 省略代碼
};

再繼續(xù)查看objc_object:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

兩個說明:

  • OC底層實現(xiàn)是C語言,所以objc_object即是NSObject的表現(xiàn)形式。
  • isa的類型為Class的原因有三點:
    • 萬物皆對象,isa是可以由Class來接受的。
    • 早期調(diào)用isa是用來返回類的,后面是通過nonpointer區(qū)分純凈isa和優(yōu)化的isa。
    • 源碼:return (Class)(isa.bits & ISA_MASK),進行了Class類型強轉(zhuǎn)。
2、objc_class分析

1)內(nèi)存占用分析:

Class ISA;   :// 8字節(jié) 
Class superclass;  :// 8字節(jié)
cache_t cache; :// 16字節(jié)

前兩個很好理解,我們順著源碼點進去可以看到都是結(jié)構(gòu)體指針,我們看一下cache_t源碼:

struct cache_t {
    struct bucket_t *_buckets;  // 8字節(jié) 注意這里是結(jié)構(gòu)體指針,指針占8字節(jié)
    mask_t _mask; // 4字節(jié) mask_t類型為uint32_t ,int占4字節(jié)
    mask_t _occupied; // 4字節(jié) mask_t類型為uint32_t ,int占4字節(jié)
    // 省略代碼
};

以上,我們可以得出前三個屬性的內(nèi)存偏移量是8+8+16=32字節(jié),這里32字節(jié)是在10進制下,在16進制下則是20字節(jié)。

2)bits存儲信息探索

在開始探索之前我們看一下Person文件的代碼:

Person.h
@interface Person : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *sex;

- (void)sayHello;
+ (void)sayHappy;

@end

Person.m
@implementation LGPerson

- (void)sayHello{
    NSLog(@"Person say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"Person say : Happy!!!");
}

NSLog處斷點,通過x/4gx打印地址

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        Class pClass = object_getClass(person); 
        NSLog(@"-------");
}

打印結(jié)果:

(lldb) x/4gx pClass
0x100002438: 0x001d800100002411 0x0000000100b37140
0x100002448: 0x00000001003da260 0x0000000000000000

通過以上對源碼的分析,我們可以知道0x001d800100002411ISA0x0000000100b37140superclass,0x00000001003da260cache0x0000000000000000bits。

上文已經(jīng)算出前三個屬性占用內(nèi)存偏移為20字節(jié),為了得到class_data_bits_t的地址,我們需要做一下地址偏移運算:0x100002438 + 0x20 = 0x100002458。

  • 首先取到class_ro_t:
(lldb) x/4gx pClass
0x100002438: 0x001d800100002411 0x0000000100b37140
0x100002448: 0x00000001003da260 0x0000000000000000
(lldb) p (class_data_bits_t *)0x100002458 // 類型轉(zhuǎn)換
(class_data_bits_t *) $1 = 0x0000000100002458
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100f55d30
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002388
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002260
        arrayAndFlag = 4294976096
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100002360
        arrayAndFlag = 4294976352
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100002388
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 32
  reserved = 0
  ivarLayout = 0x0000000100001f81 "\x03"
  name = 0x0000000100001f78 "LGPerson"
  baseMethodList = 0x0000000100002260
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000022f8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100002360
}
  • 屬性:我們的屬性是存放在baseProperties中的:
(lldb) p $5.baseProperties
(property_list_t *const) $6 = 0x0000000100002360
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 2
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}

到這里我們就取到了nickName,但是count為2,這里只看到first,那我們來嘗試取一下另一個屬性:

(lldb) p $7.get(1)
(property_t) $13 = (name = "sex", attributes = "T@\"NSString\",C,N,V_sex")
  • 成員變量:存放在ivars中:
(lldb) p $5.ivars
(const ivar_list_t *const) $8 = 0x00000001000022f8
(lldb) p *$8
(const ivar_list_t) $9 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 3
    first = {
      offset = 0x00000001000023f8
      name = 0x0000000100001e34 "hobby"
      type = 0x0000000100001f9e "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

同樣取到其他兩個元素:

(lldb) p $8->get(1)
(ivar_t) $20 = {
  offset = 0x0000000100002400
  name = 0x0000000100001e3a "_nickName"
  type = 0x0000000100001f9e "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $8->get(2)
(ivar_t) $21 = {
  offset = 0x0000000100002408
  name = 0x0000000100001e44 "_sex"
  type = 0x0000000100001f9e "@\"NSString\""
  alignment_raw = 3
  size = 8
}

如上,可以看到生成的成員變量也是存在ivars中的。

  • 方法:接下來看一下baseMethodList里存放的是啥。
(lldb) p $5.baseMethodList
(method_list_t *const) $10 = 0x0000000100002260
(lldb) p *$10
(method_list_t) $11 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 6
    first = {
      name = "sayHello"
      types = 0x0000000100001f83 "v16@0:8"
      imp = 0x0000000100001ad0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
  }
}

意料之中我們?nèi)〉搅?code>sayHello方法,但是這里count為什么是6呢?我們來繼續(xù)打?。?/p>

(lldb) p $11.get(1)
(method_t) $23 = {
  name = "sex"
  types = 0x0000000100001f8b "@16@0:8"
  imp = 0x0000000100001ba0 (LGTest`-[LGPerson sex] at LGPerson.h:18)
}
(lldb) p $11.get(2)
(method_t) $24 = {
  name = "setSex:"
  types = 0x0000000100001f93 "v24@0:8@16"
  imp = 0x0000000100001bd0 (LGTest`-[LGPerson setSex:] at LGPerson.h:18)
}
(lldb) p $11.get(3)
(method_t) $25 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f83 "v16@0:8"
  imp = 0x0000000100001c10 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)

一一取完發(fā)現(xiàn)系統(tǒng)生成的setter/getter方法以及.cxx_destruct析構(gòu)函數(shù)都是存放在baseMethodList中的,,但是并沒有看到類方法+ (void)sayHappy;。那么類方法是存在哪里呢?

isa探索一文中我們已經(jīng)對類方法的存儲做了分析,類方法是存在元類中的。

這里有兩種方式可以驗證:
1)按照上面的方法繼續(xù)用lldb來打印分析。

(lldb) x/4gx pClass
0x100002438: 0x001d800100002411 0x0000000100b37140
0x100002448: 0x00000001003da260 0x0000000000000000
// 通過isa & mask 取到 pClass的元類
(lldb) p/x 0x001d800100002411 & 0x00007ffffffffff8
(long) $29 = 0x0000000100002410
(lldb) x/4gx $29
0x100002410: 0x001d800100b370f1 0x0000000100b370f0
0x100002420: 0x0000000100f55d90 0x0000000100000003
(lldb) p (class_data_bits_t *)0x100002430
(class_data_bits_t *) $30 = 0x0000000100002430
(lldb) p $30->data()
(class_rw_t *) $32 = 0x0000000100f55cf0
(lldb) p $32->ro
(const class_ro_t *) $35 = 0x0000000100002218
(lldb) p *$35
(const class_ro_t) $36 = {
  flags = 389
  instanceStart = 40
  instanceSize = 40
  reserved = 0
  ivarLayout = 0x0000000000000000
  name = 0x0000000100001f78 "Person"
  baseMethodList = 0x00000001000021f8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
}
(lldb) p $35.baseMethodList
(method_list_t *const) $37 = 0x00000001000021f8
  Fix-it applied, fixed expression was: 
    $35->baseMethodList
(lldb) p $35->baseMethodList
(method_list_t *const) $38 = 0x00000001000021f8
(lldb) p *$38
(method_list_t) $39 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100001f83 "v16@0:8"
      imp = 0x0000000100001b00 (Test`+[Person sayHappy] at Person.m:17)
    }
  }
}

2)利用runtime提供的API來調(diào)用分析。

void testInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

輸出結(jié)果:

2020-03-16 15:44:57.692317+0800 Test[85991:2268904] 0x100002268-0x0-0x0-0x100002200
2020-03-16 15:44:57.692674+0800 Test[85991:2268904] testInstanceMethod_classToMetaclass

sayHello在類中是有值的,在元類中是沒有值的。
sayHappy在類中是沒有值的,在元類中是有值的。

三、總結(jié):

  • 類創(chuàng)建時機是在編譯期。
  • 類的本質(zhì)是對象(萬物皆對象)。
  • class_rw_t是可以在運行時來拓展類的一些屬性、方法和協(xié)議等內(nèi)容。
  • class_ro_t 是在編譯時就已經(jīng)確定了的,存儲的是類的成員變量、屬性、方法和協(xié)議等內(nèi)容。
  • 實例方法存在類中。
  • 類方法存在元類中。

lldb命令:

  • p/t: 二進制打印
  • p/o: 八進制打印
  • p/x: 十六進制打印
  • p/d: 十進制打印

以上
如有不當(dāng),歡迎指正,感謝。

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

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

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