iOS 底層探索篇 —— 類的結構分析

前言c++

連續指針內存偏移

int array[] = {1,2,4};
int *b = array;
NSLog(@"%p-%p-%p-%p",&array,&array[0],&array[1],&array[2]);
NSLog(@"%p-%p-%p",b,b+1,b+2);
        
for (int i = 0; i < 3; i++) {
      int value = *(b+i);
     NSLog(@"value-%d",value);
}

2019-12-21 21:06:11.914923+0800 XDTest[2663:291951] 0x7ffeefbff5bc-0x7ffeefbff5bc-0x7ffeefbff5c0-0x7ffeefbff5c4
2019-12-21 21:06:11.915605+0800 XDTest[2663:291951] 0x7ffeefbff5bc-0x7ffeefbff5c0-0x7ffeefbff5c4
2019-12-21 21:06:11.915673+0800 XDTest[2663:291951] value-1
2019-12-21 21:06:11.915729+0800 XDTest[2663:291951] value-2
2019-12-21 21:06:11.915756+0800 XDTest[2663:291951] value-4
複製代碼
  • 指針b的地址就是array數組的首地址。
  • 經過指針偏移能夠找到接下來的連續內存地址。

類結構分析

1. 萬物皆對象

typedef struct objc_class *Class;
struct objc_class : objc_object{};
複製代碼
  • 從源碼中咱們就能夠知道類Class的本質就是objc_class
  • objc_class繼承自objc_object,驗證萬物皆對象。

Q1: objc_classNSObject的關係?swift

NSObject就是一個類,其本質是objc_class數組

Q2: objc_objectNSObject的關係?緩存

NSObjectOC的類型,objc_objectc的類型。 NSObject是對objc_object的封裝。bash

2. 類結構

struct objc_class : objc_object {
    // Class ISA;           //8
    Class superclass;       //8
    cache_t cache;          //16        // 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();
    }
    ...省略其餘的信息...
};
複製代碼

3. 類結構成員分析

3.1 Class ISAapp

一個被註釋的成員,表明是從父類繼承過來的,所佔用8字節。less

struct objc_object {
private:
    isa_t isa;
  ...省略其餘的信息...
};
複製代碼

3.2 Class superclasside

指向父類的指針,Class自己就是一個指針,所佔用8字節。函數

3.3 cache_t cachepost

顧名思義,是存儲緩存的對象,所佔用16字節

truct cache_t {
    struct bucket_t *_buckets;  //指針佔用8字節
    mask_t _mask;               // int32  佔用4字節
    mask_t _occupied;           //佔用4字節

 ...省略其餘的信息...
};
複製代碼

3.4 class_data_bits_t bits

一個結構體,咱們發現類的一些相關的信息在前面三個成員裏面都看不到,有此咱們能夠分析出來,類的相關屬性,成員變量,方法都在這個結構體裏面。

4. class_data_bits_t深刻

經過對類的結構體的分析isasuperclasscache這些屬性裏面是看不到與咱們自定義的屬性等等有關係的,所以咱們對準bits來探索。

  1. 咱們在類的結構體也看到了下面的這行代碼
class_rw_t *data() { 
        return bits.data();
    }
複製代碼

驗證了類的相關信息都存在bits裏面,而且經過bits.data()函數能夠直接獲取到結構體class_rw_t的信息。

  1. 觀察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;

...省略其餘的信息...
};
複製代碼

咱們彷佛看到了咱們熟悉的屬性,方法,代理等相關的數據類型。 可是這裏要注意了,咱們類的屬性,方法並無存在method_array_tproperty_array_t這些類型的屬性裏面(它是什麼,後面章節會介紹)。 而是存放在了class_ro_t這個結構體裏面,咱們看到定義的是const,能夠說明這一塊會在編譯時候就肯定好了,後面取出來使用是不能夠更改的。

  1. 觀察class_ro_t結構體
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;
   ...省略其餘的信息...
複製代碼
  • 成員變量存放const ivar_list_t * ivars
  • 屬性存放property_list_t *baseProperties
  • 方法存放method_list_t * baseMethodList

5. 屬性-方法的歸屬探索

5.1 準備條件

定義成員變量、屬性、對象方法、類方法。

@interface XDPerson : NSObject
{
    NSString *otherName;
}
@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;

+ (void)sayHappy;
@end
複製代碼

5.2 lldb調試

在文章的開始已經瞭解了在連續內存段裏面能夠經過指針偏移找到對應的內存段。

  1. 查看類XDPerson的內存信息
(lldb) x/4gx XDPerson.class
0x100001318: 0x001d8001000012f1 0x0000000100aff140
0x100001328: 0x000000010203ce00 0x0000000200000003
複製代碼
  1. 咱們知道信息在objc_class結構體的bits屬性裏面,經過內存地址偏移來找到bits的內存地址,咱們直接去找0x100001318+32個字節=0x100001338
(lldb) p (class_data_bits_t *)0x100001338
(class_data_bits_t *) $1 = 0x0000000100001338
複製代碼
  1. 尋找class_rw_t經過bits.data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010203cd50
複製代碼
  1. 查看class_rw_t內存數據信息
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148139008
  version = 0
  ro = 0x00000001000011f0
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100001128
        arrayAndFlag = 4294971688
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x00000001000011d8
        arrayAndFlag = 4294971864
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000000000000 <no value available>
}
複製代碼
  1. 查看目標ro
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x00000001000011f0
複製代碼
  1. 查看class_ro_t的內存數據信息
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000f80 "\x02"
  name = 0x0000000100000f77 "XDPerson"
  baseMethodList = 0x0000000100001128
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100001190
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x00000001000011d8
  _swiftMetadataInitializer_NEVER_USE = {}
}
複製代碼
  1. 查看baseProperties
(lldb) p $5.baseProperties
(property_list_t *const) $6 = 0x00000001000011d8
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "nickName", attributes = "T@"NSString",C,N,V_nickName")
  }
}
複製代碼

有且僅有一個元素 nickName和咱們定義的相同。

  1. 查看ivars
(lldb) p $5.ivars
(const ivar_list_t *const) $8 = 0x0000000100001190
(lldb) p *$8
(const ivar_list_t) $9 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000012e8
      name = 0x0000000100000f20 "otherName"
      type = 0x0000000100000fa7 "@"NSString""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $9.get(0)
(ivar_t) $10 = {
  offset = 0x00000001000012e8
  name = 0x0000000100000f20 "otherName"
  type = 0x0000000100000fa7 "@"NSString""
  alignment_raw = 3
  size = 8
}
(lldb) p $9.get(1)
(ivar_t) $11 = {
  offset = 0x00000001000012e0
  name = 0x0000000100000f2a "_nickName"
  type = 0x0000000100000fa7 "@"NSString""
  alignment_raw = 3
  size = 8
}
複製代碼

有兩個元素,一個是成員變量otherName,另外一個是編譯生成的成員變量_nickName

  1. 查看baseMothedList
(lldb) p $5.baseMethodList
(method_list_t *const) $12 = 0x0000000100001128
(lldb) p *$12
(method_list_t) $13 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100000f8c "v16@0:8"
      imp = 0x0000000100000d70 (XDTest`-[XDPerson sayHello] at XDPerson.m:11)
    }
  }
}
(lldb) p $13.get(0)
(method_t) $14 = {
  name = "sayHello"
  types = 0x0000000100000f8c "v16@0:8"
  imp = 0x0000000100000d70 (XDTest`-[XDPerson sayHello] at XDPerson.m:11)
}
(lldb) p $13.get(1)
(method_t) $15 = {
  name = "nickName"
  types = 0x0000000100000f94 "@16@0:8"
  imp = 0x0000000100000d90 (XDTest`-[XDPerson nickName] at XDPerson.h:16)
}
(lldb) p $13.get(2)
(method_t) $16 = {
  name = "setNickName:"
  types = 0x0000000100000f9c "v24@0:8@16"
  imp = 0x0000000100000dc0 (XDTest`-[XDPerson setNickName:] at XDPerson.h:16)
}
(lldb) p $13.get(3)
(method_t) $17 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f8c "v16@0:8"
  imp = 0x0000000100000e00 (XDTest`-[XDPerson .cxx_destruct] at XDPerson.m:10)
}
複製代碼
  • sayHello定義的對象方法。
  • nickName底層生成的getter方法。
  • setNickName:底層生成的setter方法。
  • .cxx_destruct系統c++的析構函數。

這裏也驗證了屬性在底層生成了 ivar+getter+setter

  1. sayHappy類方法去哪了?

經過iOS 底層探索篇 —— isa的初始化&指向分析這篇文章的分析,咱們能夠了解到類在元類那裏也至關因而一個對象,那類的類方法會不會在元類那裏也相似一個對象方法給存起來了?

...省略部分調試 去查找元類的相關信息...
(lldb) p $25.baseMethodList
(method_list_t *const) $26 = 0x00000001000010c0
(lldb) p *$26
(method_list_t) $27 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
    name = "sayHappy"
    types = 0x0000000100000f8c "v16@0:8"
    imp = 0x0000000100000d80 (XDTest`+[XDPerson sayHappy] at XDPerson.m:15)
    }
  }
}
複製代碼

sayHappy類方法在元類裏面找到了。

驗證了咱們的成員遍歷,屬性,方法都在class_ro_t的這個機構體裏面找到了。

其實筆者也去找了一下協議,可是很惋惜沒有找到,後面會在單獨研究協議,原諒能力有限,不足之處還請指出。

其餘結構體分析

在上面的分析中咱們看到了property_tivar_tmethod_t這三個結構體。接下來咱們作一下簡單的分析。

1. property_t

struct property_t {
    const char *name;
    const char *attributes;
};
複製代碼
  • name屬性的名字;
  • attributes描述字段;

2. ivar_t

struct ivar_t {
#if __x86_64__
    // *offset was originally 64-bit on some x86_64 platforms.
    // We read and write only 32 bits of it.
    // Some metadata provides all 64 bits. This is harmless for unsigned 
    // little-endian values.
    // Some code uses all 64 bits. class_addIvar() over-allocates the 
    // offset for their benefit.
#endif
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};
複製代碼
  • offset偏移內存地址;
  • name名字;
  • type類型;
  • alignment_raw對齊信息;
  • size所佔字節;

3. method_t

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;  //using MethodListIMP = IMP;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};
複製代碼
  • name方法名;
  • types方法前面;
  • imp方法實現;
相關文章
相關標籤/搜索