OC 類探索(一)

一册养、isa->類和元類

上篇文章分析了對(duì)象的isa底層實(shí)現(xiàn)以及是如何與cls關(guān)聯(lián)的,這邊文章繼續(xù)分析類的結(jié)構(gòu)混埠。

HPObject *obj = [HPObject alloc];

對(duì)obj打斷點(diǎn)查看:

image.png

  • x/4gx獲取objisa指針誓酒。
  • isa & mask獲取類對(duì)象HPObject
  • po驗(yàn)證獲取到的是HPObject貌亭。

這個(gè)時(shí)候如果對(duì)類對(duì)象繼續(xù)x/4gx呢柬唯?

image.png

發(fā)現(xiàn)HPObjectisa &mask后也是HPObject,但是兩者的地址不一樣圃庭。并且類對(duì)象MASK前后地址沒(méi)有變化說(shuō)明類對(duì)象的isa是一個(gè)單純的指針锄奢,沒(méi)有位域信息。

1.1 類對(duì)象內(nèi)存?zhèn)€數(shù)

既然上面驗(yàn)證出來(lái)類對(duì)象也是會(huì)開(kāi)辟空間的冤议,那么類對(duì)象在內(nèi)存中有多少份呢斟薇?
驗(yàn)證代碼:

void verifyClassNumber() {
    Class class1 = [HPObject class];
    Class class2 = [HPObject alloc].class;
    Class class3 = object_getClass([HPObject alloc]);
    Class class4 = objc_getClass("HPObject");
    //再次創(chuàng)建對(duì)象
    Class class5 = [HPObject alloc].class;
    NSLog(@"\n%p\n%p\n%p\n%p\n%p",class1,class2,class3,class4,class5);
}

結(jié)果:

0x1000082f0
0x1000082f0
0x1000082f0
0x1000082f0
0x1000082f0

可以看到都指向一個(gè)內(nèi)存地址,所以只存在一份恕酸。這幾種獲取類對(duì)象的方式區(qū)別如下:


獲取類對(duì)象對(duì)比

上面通過(guò)HPObjectisa &mask后也是HPObject堪滨,但是兩者的地址不一樣。說(shuō)明類對(duì)象的isa指針獲取的不是類對(duì)象蕊温。那么它是什么袱箱?(元類)。
這個(gè)時(shí)候可以用MachOView查看下符號(hào)表:

image.png

看到有一個(gè)METACLASS_HPObject义矛,但是元類并不是我們創(chuàng)建的发笔。那么意味著是系統(tǒng)生成和編譯的。
所以就有對(duì)應(yīng)關(guān)系:實(shí)例對(duì)象(isa)->類對(duì)象(isa)->元類凉翻。

二了讨、isa走位圖和繼承鏈

2.1 isa走位圖

上面得到了實(shí)例對(duì)象(isa)->類對(duì)象(isa)->元類,這個(gè)時(shí)候又有一個(gè)疑問(wèn)制轰,元類的isa指向哪里呢前计?

image.png

  • 實(shí)例對(duì)象(isa)->類對(duì)象(sia)->元類(isa)->根元類(NSObject isa)->自身
  • 根類實(shí)例對(duì)象(isa)->根元類(isa)->自身

代碼驗(yàn)證下:

void verifyIsaLinked() {
    //NSObject 實(shí)例對(duì)象
    NSObject *obj = [NSObject alloc];
    //NSObject類
    Class class = object_getClass(obj);
    //NSObject元類
    Class metaClass = object_getClass(class);
    //NSObject根元類
    Class rootMetaClass = object_getClass(metaClass);
    //NSObject根根元類
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n實(shí)例對(duì)象:%p \n類:%p \n元類:%p \n根元類:%p \n根根元類:%p",obj,class,metaClass,rootMetaClass,rootRootMetaClass);
}

結(jié)果:

實(shí)例對(duì)象:0x10136bf30 
類:0x100358140 
元類:0x1003580f0 
根元類:0x1003580f0 
根根元類:0x1003580f0

這樣就得到了isa走位圖:

isa走位圖

2.2 類和元類的繼承鏈

上面分析到了元類,那么元類有父類么垃杖?

HPObject元類的父類

Class hpMetaClass = object_getClass(HPObject.class);
//HPObject元類的父類
Class hpSuperMetaClass = class_getSuperclass(hpMetaClass);
NSLog(@"\nHPObject元類的父類:%@ - %p",hpSuperMetaClass,hpSuperMetaClass);
//NSObject元類
Class objMetaClass = object_getClass(NSObject.class);
NSLog(@"\nNSObject元類:%@ - %p",objMetaClass,objMetaClass);

結(jié)果:

HPObject元類的父類:NSObject - 0x1003580f0
NSObject元類:NSObject - 0x1003580f0

可以得出結(jié)論:HPObject元類的父類是NSObject的元類男杈。

HPObject子類(HPSubobject)元類的父類
新建一個(gè)HPObject的子類HPSubobject同樣獲取它的元類的父類:

///HPObject元類
Class hpMetaClass = object_getClass(HPObject.class);
NSLog(@"\nHPObject元類:%@ - %p",hpMetaClass,hpMetaClass);
//HPSubobject元類
Class hpsMetaClass = object_getClass(HPSubobject.class);
//HPSubobject元類的父類
Class hpsSuperMetaClass = class_getSuperclass(hpsMetaClass);
NSLog(@"\nHPSubobject元類的父類:%@ - %p",hpsSuperMetaClass,hpsSuperMetaClass);

結(jié)果:

HPObject元類:HPObject - 0x1000083b0
HPSubobject元類的父類:HPObject - 0x1000083b0

所以 元類也有繼承鏈

NSObject(根元類)的父類
那么NSObject的元類也就是根元類的父類呢调俘?

//NSObject 實(shí)例對(duì)象
NSObject *obj = [NSObject alloc];
//NSObject類
Class class = object_getClass(obj);
//NSObject元類
Class metaClass = object_getClass(class);
//NSObject元類的父類
Class superMetaClass = class_getSuperclass(metaClass);
NSLog(@"\n類:%@ - %p \n元類的父類:%@ - %p",class,class,superMetaClass,superMetaClass);

結(jié)果:

類:NSObject - 0x100358140 
元類的父類:NSObject - 0x100358140

可以看到是根元類的父類是NSObject伶棒,萬(wàn)物基于NSObject旺垒。至此元類的繼承鏈就清晰了。

類的繼承關(guān)系
已知HPSubobject->HPObject->NSObject肤无,需要驗(yàn)證NSObject的父類:

Class objSuperClass = class_getSuperclass(NSObject.class);
NSLog(@"\n%@ - %p",objSuperClass,objSuperClass);

結(jié)果:

(null) - 0x0

所以NSObject不存在父類先蒋。

這樣就得到了完整的類和元類的繼承鏈:


類的繼承鏈

通過(guò)isa的走位鏈和類的繼承關(guān)系就得到了那張?zhí)O果官網(wǎng)著名的圖:

isa流程圖.jpg

三、源碼分析類結(jié)構(gòu)

3.1類的內(nèi)存結(jié)構(gòu)

既然類也有isa舅锄,那么類的結(jié)構(gòu)是怎樣的呢鞭达?類在底層是objc_class類型,在runtime.h(OBJC2_UNAVAILABLE)與objc-runtime-new.h中都有聲明』史蓿現(xiàn)在使用的都是objc-runtime-new.h中的objc_class畴蹭。它是一個(gè)結(jié)構(gòu)體要研究它的結(jié)構(gòu)需要看它的成員變量。
類的結(jié)構(gòu)如下:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
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
};
  • ISA:繼承自objc_object鳍烁,指向元類叨襟。
  • superclass:指向父類。
  • cache:方法緩存幔荒,當(dāng)調(diào)用一次方法后就會(huì)緩存進(jìn)vtable中糊闽,加速下次調(diào)用。
  • bits:具體類信息(成員變量爹梁、屬性右犹、方法)。

在源碼中能找到class_rw_t中封裝了獲取methods姚垃、properties念链、protocols的方法。而class_rw_t是存在bits中的积糯。那么怎么通過(guò)objc_class獲取bits數(shù)據(jù)呢掂墓?

3.2 類的結(jié)構(gòu)內(nèi)存計(jì)算

既然類的底層數(shù)據(jù)是結(jié)構(gòu)體,那么只要找到首地址通過(guò)偏移就能得到bits數(shù)據(jù)的地址看成。ISAsuperclass都是結(jié)構(gòu)體指針?lè)謩e占用8字節(jié)君编,那么cache占多大空間呢?

cache_t中內(nèi)容很多包括很多函數(shù)和static的常量(不占結(jié)構(gòu)體空間)川慌,其實(shí)只需要關(guān)注成員變量即可吃嘿。
cache_t結(jié)構(gòu)如下:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //unsigned long 8字節(jié)
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;//uint32_t 4字節(jié)
#if __LP64__
            uint16_t                   _flags;//2字節(jié)
#endif
            uint16_t                   _occupied;//2字節(jié)
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;//指針 8字節(jié)
    };
};

cache_t包含兩部分_bucketsAndMaybeMaskunsigned long 8字節(jié))與聯(lián)合體,聯(lián)合體中有一個(gè)_originalPreoptCache(指針 8字節(jié))與結(jié)構(gòu)體(_originalPreoptCache與結(jié)構(gòu)體只需要計(jì)算一個(gè)梦重,共用一塊內(nèi)存)兑燥。所以cache_t大小為16字節(jié)。

那么只需要類的首地址偏移32字節(jié)(0x20 = ISA(8) + superclass (8) + cache (16))就能得到bits的地址忍饰。

指針的步長(zhǎng)與指針類型有關(guān)贪嫂。

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

3.3.1 ISA

image.png
  • 類的isa就是一個(gè)純指針寺庄,指向元類艾蓝。

3.3.2 superclass

image.png
  • 類的superclass就是類的父類力崇。

3.3.3 cache

cache方法緩存相關(guān)的內(nèi)容cache

3.3.4 bits

bits的類型是class_data_bits_t結(jié)構(gòu)如下:

struct class_data_bits_t {
    uintptr_t bits;
};

在源碼中有class_rw_t * plus custom rr/alloc flags注釋赢织,也就是說(shuō)class_data_bits_t的核心是class_rw_t亮靴。查找源碼發(fā)現(xiàn)data()返回的是class_rw_t*類型。

data()

data()實(shí)現(xiàn)如下:

#if __LP64__
#define FAST_DATA_MASK        0x00007ffffffffff8UL
#else
#define FAST_DATA_MASK        0xfffffffcUL
#endif


class_rw_t* data() {
   return (class_rw_t *)(bits & FAST_DATA_MASK);
}
  • 64位下data()占用44[3~46]于置,32位下data()占用30[2~31]茧吊。
  • 所以也就是bits(class_data_bits_t)的[3~46]/[2~31]位是data()(class_rw_t)。

同理源碼中有以下代碼:

#if __LP64__

// class is a Swift class from the pre-stable Swift ABI
#define FAST_IS_SWIFT_LEGACY    (1UL<<0)
// class is a Swift class from the stable Swift ABI
#define FAST_IS_SWIFT_STABLE    (1UL<<1)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<2)
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL

#else

// class is a Swift class from the pre-stable Swift ABI
#define FAST_IS_SWIFT_LEGACY  (1UL<<0)
// class is a Swift class from the stable Swift ABI
#define FAST_IS_SWIFT_STABLE  (1UL<<1)
#define FAST_DATA_MASK        0xfffffffcUL

#endif // __LP64__


bool isSwiftStable() {
    return getBit(FAST_IS_SWIFT_STABLE);
}

bool isSwiftLegacy() {
    return getBit(FAST_IS_SWIFT_LEGACY);
}

bool hasCustomRR() const {
    return !bits.getBit(FAST_HAS_DEFAULT_RR);
}
  • FAST_IS_SWIFT_LEGACY:第0位類是否來(lái)自穩(wěn)定的Swift ABISwift 類八毯。(遺留的類)
  • FAST_IS_SWIFT_STABLE:第1位類是否來(lái)自穩(wěn)定的Swift ABISwift 類搓侄。
  • FAST_HAS_DEFAULT_RR:第2位判斷當(dāng)前類或者父類是否含有默認(rèn)的retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference方法。(僅64位)

前面已經(jīng)得出結(jié)論類的首地址偏移32字節(jié)就能得到bits的地址:

image.png

  • 首地址 + 偏移 得到 bits的地址$8话速。
  • 強(qiáng)轉(zhuǎn)bits地址($8)為class_data_bits_t指針($9)讶踪。
  • bits指針($9)調(diào)用data()函數(shù)獲取class_rw_t指針($10)。
  • 打印class_rw_t指針?biāo)赶虻闹担?code>*$10)泊交。

class_rw_t

既然data()class_rw_t結(jié)構(gòu)乳讥,它的內(nèi)存結(jié)構(gòu)如下:

struct class_rw_t {
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
};
  • flags
  • witness
  • index32位下有效,記錄類在數(shù)組中的索引廓俭。
  • ro_or_rw_ext:存儲(chǔ)屬性云石,方法,協(xié)議研乒,成員變量汹忠。
  • firstSubclass:第一個(gè)子類。
    在上面的調(diào)試中firstSubclassnil是因?yàn)樗鼪](méi)有被使用告嘲。是懶加載類错维,如果使用了或者實(shí)現(xiàn)了+ load方法則會(huì)指向子類。
  • nextSiblingClass:相鄰類橄唬。

分析到這里顯然核心就是ro_or_rw_ext了赋焕,查看class_rw_t源碼發(fā)現(xiàn)提供了methods、properties仰楚、protocols方法隆判。

修改HPObject以及添加方法,文件如下:

image.png

HPObject:

@interface HPObject : NSObject {
    int height;
    NSString *sex;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)instanceMethod;
+ (void)classMethod;

@end

HPObject+Additions1:

@interface HPObject (Additions1)

- (void)additions1InstanceMethod;

+ (void)additions1ClassMethod;

@end

HPObject+additions2:

@interface HPObject (additions2)

- (void)additions2InstanceMethod;

+ (void)additions2ClassMethod;

@end

??:方法要有對(duì)應(yīng)的實(shí)現(xiàn)僧界。

properties()

(lldb) x/6gx HPObject.class
0x1000083d8: 0x00000001000083b0 0x0000000100358140
0x1000083e8: 0x000000010034f360 0x0000803400000000
0x1000083f8: 0x00000001032babd4 0x00000001000ac920
(lldb) p (class_data_bits_t *)0x1000083f8
(class_data_bits_t *) $1 = 0x00000001000083f8
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001032babd0
(lldb) p $2->properties()
(const property_array_t) $3 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008308
      }
      arrayAndFlag = 4295000840
    }
  }
}

通過(guò)properties獲取到的屬性是一個(gè)property_array_t侨嘀,結(jié)構(gòu)如下:

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

property_array_t繼承自list_array_tt是一個(gè)兩層結(jié)構(gòu)property_list_t < property_t>list_array_tt中有iterator意味著它有遍歷能力捂襟。
通過(guò)list能獲取到RawPtr<property_list_t>

(lldb) p $3.list
(const RawPtr<property_list_t>) $4 = {
  ptr = 0x0000000100008308
}

通過(guò)訪問(wèn)ptr能夠獲取到property_list_t數(shù)組:

(lldb) p $4.ptr
(property_list_t *const) $5 = 0x0000000100008308
(lldb) p *$5
(property_list_t) $6 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}

$5就相當(dāng)于迭代器了,property_list_t源碼結(jié)構(gòu)如下:

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

property_list_t是一個(gè)空實(shí)現(xiàn)繼承自entsize_list_tt咬腕,entsize_list_tt中有一個(gè)get方法:

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
};

所以可以根據(jù)get方法獲取元素:

(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $6.get(1)
(property_t) $8 = (name = "age", attributes = "Ti,N,V_age")
(lldb) p $6.get(2)
Assertion failed: (i < count), function get, file /Volumes/HOTPOTCAT/sourcecode/objc4/objc4-818.2/runtime/objc-runtime-new.h, line 625.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

這個(gè)時(shí)候并沒(méi)有成員變量heightsex。只有nameage兩個(gè)屬性葬荷。

methods()

properties相同涨共,通過(guò)class_rw_tmethods方法獲取方法:

(lldb) p $2->methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008098
      }
      arrayAndFlag = 4295000216
    }
  }
}
(lldb) p $3.list.ptr
(method_list_t *const) $4 = 0x0000000100008098
(lldb) p *$4
(method_list_t) $5 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 8)
}
(lldb) p $5.get(0)
(method_t) $6 = {}
  • 看到有8個(gè)方法纽帖。
  • 由于method_list_t也是繼承自entsize_list_tt,所以直接通過(guò)get()獲取举反,結(jié)果返回空懊直。

那么分別看下property_tmethod_t的實(shí)現(xiàn)。
property_t:

struct property_t {
    const char *name;
    const char *attributes;
};

method_t:

struct method_t {
//......
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
//......
};

兩者的區(qū)別是property_t有成員變量火鼻,method_t沒(méi)有成員變量室囊。索引method_t打印為空。但是method_t中有一個(gè)結(jié)構(gòu)體big中有nameimp魁索,并且提供了一個(gè)big方法融撞,所以可以通過(guò)big方法獲取:

(lldb) p $5.get(0).big()
(method_t::big) $7 = {
  name = "additions1InstanceMethod"
  types = 0x0000000100003f3f "v16@0:8"
  imp = 0x0000000100003c30 (HPObjcTest`-[HPObject(Additions1) additions1InstanceMethod])
}
(lldb) p $5.get(1).big()
(method_t::big) $8 = {
  name = "instanceMethod"
  types = 0x0000000100003f3f "v16@0:8"
  imp = 0x0000000100003c50 (HPObjcTest`-[HPObject instanceMethod])
}
(lldb) p $5.get(2).big()
(method_t::big) $9 = {
  name = "additions2InstanceMethod"
  types = 0x0000000100003f3f "v16@0:8"
  imp = 0x0000000100003d50 (HPObjcTest`-[HPObject(additions2) additions2InstanceMethod])
}
(lldb) p $5.get(3).big()
(method_t::big) $10 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f3f "v16@0:8"
  imp = 0x0000000100003d00 (HPObjcTest`-[HPObject .cxx_destruct])
}
(lldb) p $5.get(4).big()
(method_t::big) $11 = {
  name = "name"
  types = 0x0000000100003f55 "@16@0:8"
  imp = 0x0000000100003c60 (HPObjcTest`-[HPObject name])
}
(lldb) p $5.get(5).big()
(method_t::big) $12 = {
  name = "setName:"
  types = 0x0000000100003f5d "v24@0:8@16"
  imp = 0x0000000100003c90 (HPObjcTest`-[HPObject setName:])
}
(lldb) p $5.get(6).big()
(method_t::big) $13 = {
  name = "age"
  types = 0x0000000100003f68 "i16@0:8"
  imp = 0x0000000100003cc0 (HPObjcTest`-[HPObject age])
}
(lldb) p $5.get(7).big()
(method_t::big) $14 = {
  name = "setAge:"
  types = 0x0000000100003f70 "v20@0:8i16"
  imp = 0x0000000100003ce0 (HPObjcTest`-[HPObject setAge:])
}
  • 除了兩個(gè)屬性的4個(gè)setter + getter方法外還有類和分類的實(shí)例方法以及. cxx_destruct粗蔚。

protocols()

(lldb)  x/6gx HPObject.class
0x1000088a0: 0x0000000100008878 0x000000010036b140
0x1000088b0: 0x0000000100362360 0x0000803c00000000
0x1000088c0: 0x0000000100637d64 0x00000001000b9980
(lldb) p (class_data_bits_t *)0x1000088c0
(class_data_bits_t *) $16 = 0x00000001000088c0
(lldb) p $16->data()
(class_rw_t *) $17 = 0x0000000100637d60
(lldb) p $17->protocols()
(const protocol_array_t) $18 = {
  list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008678
      }
      arrayAndFlag = 4295001720
    }
  }
}

protocols()獲取的是protocol_array_t:

class protocol_array_t : 
    public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
{
    typedef list_array_tt<protocol_ref_t, protocol_list_t, RawPtr> Super;

 public:
    protocol_array_t() : Super() { }
    protocol_array_t(protocol_list_t *l) : Super(l) { }
};

繼承自list_array_tt與其屬性和方法一致懦铺。通過(guò)list.ptr能獲取到protocol_list_t

(lldb) p $18.list.ptr
(protocol_list_t *const) $19 = 0x0000000100008678
(lldb) p *$19
(protocol_list_t) $20 = (count = 1, list = protocol_ref_t [] @ 0x00007fb14e4f68b8)

結(jié)構(gòu)如下:

struct protocol_list_t {
    // count is pointer-sized by accident.
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size
};

它沒(méi)有繼承自entsize_list_ttprotocol_ref_t是個(gè)無(wú)符號(hào)長(zhǎng)整形:

typedef uintptr_t protocol_ref_t;  // protocol_t *, but unremapped

嘗試用get函數(shù)獲取元素:

(lldb) p $20.get(0).big()
error: <user expression 22>:1:5: no member named 'get' in 'protocol_list_t'
$20.get(0).big()
~~~ ^
(lldb) p $20.get(0)
error: <user expression 23>:1:5: no member named 'get' in 'protocol_list_t'
$20.get(0)
~~~ ^

果然都失敗了支鸡,可以看到protocol_ref_t既然不是個(gè)結(jié)構(gòu)體所以沒(méi)有名字相關(guān)的數(shù)據(jù)冬念。沒(méi)有繼承自entsize_list_tt所以沒(méi)有get函數(shù)。protocol_ref_t的注視看著與protocol_t有關(guān)牧挣,
查看下protocol_t的結(jié)構(gòu):

struct protocol_t : objc_object {
    const char *mangledName;
    struct protocol_list_t *protocols;
    method_list_t *instanceMethods;
    method_list_t *classMethods;
    method_list_t *optionalInstanceMethods;
    method_list_t *optionalClassMethods;
    property_list_t *instanceProperties;
    uint32_t size;   // sizeof(protocol_t)
    uint32_t flags;
    // Fields below this point are not always present on disk.
    const char **_extendedMethodTypes;
    const char *_demangledName;
    property_list_t *_classProperties;
};

現(xiàn)在的問(wèn)題就是protocol_ref_t怎么轉(zhuǎn)變成protocol_t急前。
搜索后發(fā)現(xiàn)在remapProtocolprotocol_ref_t直接強(qiáng)轉(zhuǎn)成了protocol_t:

image.png

(lldb) p $20.list[0]
(protocol_ref_t) $21 = 4295002416
(lldb) p (protocol_t *)$21
(protocol_t *) $22 = 0x0000000100008930
(lldb) p *$22
(protocol_t) $23 = {
  objc_object = {
    isa = {
      bits = 4298551496
      cls = Protocol
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 537318937
        magic = 0
        weakly_referenced = 0
        unused = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  mangledName = 0x0000000100003c13 "HPObjectProtocol"
  protocols = 0x00000001000085c8
  instanceMethods = 0x00000001000085e0
  classMethods = 0x0000000100008600
  optionalInstanceMethods = 0x0000000000000000
  optionalClassMethods = 0x0000000000000000
  instanceProperties = 0x0000000000000000
  size = 96
  flags = 0
  _extendedMethodTypes = 0x0000000100008620
  _demangledName = 0x0000000000000000
  _classProperties = 0x0000000000000000
}

這樣就獲取到了protocol_t中的數(shù)據(jù)。

protocol_t數(shù)據(jù)結(jié)構(gòu)獲绕俟埂:bits->data()->protocols().list.ptr.list[0]->(protocol_t *)強(qiáng)轉(zhuǎn)裆针。

instanceMethods

(lldb) p $23.instanceMethods
(method_list_t *) $25 = 0x00000001000085e0
(lldb) p *$25
(method_list_t) $26 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $26.get(0).big()
(method_t::big) $27 = {
  name = "protocolInstanceMethod"
  types = 0x0000000100003eb5 "v16@0:8"
  imp = 0x0000000000000000
}
  • 實(shí)例方法的獲取與methods相同。

protocols
可以看到protocol_t中有protocols那么它是什么數(shù)據(jù)呢寺晌?

(lldb) p $23.protocols
(protocol_list_t *) $24 = 0x00000001000085c8
(lldb) p *$24
(protocol_list_t) $28 = (count = 1, list = protocol_ref_t [] @ 0x00007fb151e12bf8)
(lldb) p  $28.list[0]
(protocol_ref_t) $31 = 4295002320
(lldb) p (protocol_t *)$31
(protocol_t *) $32 = 0x00000001000088d0
(lldb) p *$32
(protocol_t) $33 = {
  objc_object = {
    isa = {
      bits = 0
      cls = nil
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 0
        magic = 0
        weakly_referenced = 0
        unused = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  mangledName = 0x0000000100003c0a "NSObject"
  protocols = 0x0000000000000000
  instanceMethods = 0x00000001000082f0
  classMethods = 0x0000000000000000
  optionalInstanceMethods = 0x00000001000084c0
  optionalClassMethods = 0x0000000000000000
  instanceProperties = 0x00000001000084e0
  size = 96
  flags = 0
  _extendedMethodTypes = 0x0000000100008528
  _demangledName = 0x0000000000000000
  _classProperties = 0x0000000000000000
}

可以看到是屬于NSObject的世吨。這個(gè)時(shí)候protocols就沒(méi)有指向了。instanceMethods還仍然有數(shù)據(jù):

(lldb) p $33.instanceMethods
(method_list_t *) $34 = 0x00000001000082f0
(lldb) p *$34
(method_list_t) $35 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 19)
}
(lldb) p $35.get(0).big()
(method_t::big) $36 = {
  name = "isEqual:"
  types = 0x0000000100003ebd "c24@0:8@16"
  imp = 0x0000000000000000
}
(lldb) p $35.get(1).big()
(method_t::big) $37 = {
  name = "class"
  types = 0x0000000100003ec8 "#16@0:8"
  imp = 0x0000000000000000
}
(lldb) p $35.get(2).big()
(method_t::big) $38 = {
  name = "self"
  types = 0x0000000100003ed0 "@16@0:8"
  imp = 0x0000000000000000
}

可以看到有19個(gè)方法呻征,都有哪些呢耘婚?如下圖:

image.png

沒(méi)有@optionaldebugDescription。這也正常因?yàn)檫€有optionalInstanceMethodsoptionalClassMethods陆赋。

classMethods

(lldb) p $57.get(0).big()
(method_t::big) $58 = {
  name = "protocolClassMethod"
  types = 0x0000000100003eb5 "v16@0:8"
  imp = 0x0000000000000000
}

??只要遵循協(xié)議就能關(guān)聯(lián)到沐祷,不需要實(shí)現(xiàn)方法。本質(zhì)上與對(duì)象通過(guò)isa關(guān)聯(lián)cls相同攒岛。協(xié)議在底層也繼承自objc_object赖临。也有isa的數(shù)據(jù)結(jié)構(gòu)。也可以添加屬性灾锯。

ro()

properties中并沒(méi)有找到實(shí)例變量兢榨,但是在class_rw_tmethods()附近發(fā)現(xiàn)了ro(),它返回class_ro_t類型:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

發(fā)現(xiàn)class_rw_t中有ivar

(lldb) p $2->ro()
(const class_ro_t *) $3 = 0x0000000100008238
(lldb) p *$3
(const class_ro_t) $4 = {
  flags = 388
  instanceStart = 8
  instanceSize = 40
  reserved = 0
   = {
    ivarLayout = 0x0000000100003e7a "\x11\x11"
    nonMetaclass = 0x0000000100003e7a
  }
  name = {
    std::__1::atomic<const char *> = "HPObject" {
      Value = 0x0000000100003e71 "HPObject"
    }
  }
  baseMethodList = 0x0000000100008098
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008280
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008308
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $4.ivars
(const ivar_list_t *const) $5 = 0x0000000100008280
(lldb) p *$5
(const ivar_list_t) $6 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
  • ivars結(jié)構(gòu)為ivar_list_t類型吵聪,也是繼承自entsize_list_tt誊册,所以同樣應(yīng)該能夠根據(jù)get()去獲取ivar_t
  • 4個(gè)成員變量暖璧。

ivar_t結(jié)構(gòu)如下:

struct ivar_t {
    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;
    }
};

獲取實(shí)例變量:

(lldb) p $6.get(0)
(ivar_t) $7 = {
  offset = 0x0000000100008340
  name = 0x0000000100003ec3 "height"
  type = 0x0000000100003f47 "i"
  alignment_raw = 2
  size = 4
}
(lldb) p $6.get(1)
(ivar_t) $8 = {
  offset = 0x0000000100008348
  name = 0x0000000100003eca "sex"
  type = 0x0000000100003f49 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $6.get(2)
(ivar_t) $9 = {
  offset = 0x0000000100008350
  name = 0x0000000100003ece "_age"
  type = 0x0000000100003f47 "i"
  alignment_raw = 2
  size = 4
}
(lldb) p $6.get(3)
(ivar_t) $10 = {
  offset = 0x0000000100008358
  name = 0x0000000100003ed3 "_name"
  type = 0x0000000100003f49 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

類方法

methods()中并沒(méi)有找到類方法,那么類方法存儲(chǔ)在哪里呢君旦?根據(jù)isa的走位直接進(jìn)去元類查看澎办。元類的結(jié)構(gòu)也是objc_class所以可以嘗試同樣的方式獲取。

(lldb) x/4gx HPObject.class
0x1000083d8: 0x00000001000083b0 0x0000000100358140
0x1000083e8: 0x000000010034f360 0x0000803400000000
(lldb) x/6gx 0x00000001000083b0
0x1000083b0: 0x00000001003580f0 0x00000001003580f0
0x1000083c0: 0x0000000102930780 0x0002e03500000003
0x1000083d0: 0x00000001029303e4 0x00000001000083b0
(lldb) p (class_data_bits_t *)0x1000083d0
(class_data_bits_t *) $1 = 0x00000001000083d0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001029303e0
(lldb) p $2->methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008048
      }
      arrayAndFlag = 4295000136
    }
  }
}
(lldb) p $3.list.ptr
(method_list_t *const) $4 = 0x0000000100008048
(lldb) p *$4
(method_list_t) $5 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3)
}
(lldb) p $5.get(0).big()
(method_t::big) $6 = {
  name = "additions1ClassMethod"
  types = 0x0000000100003f3f "v16@0:8"
  imp = 0x0000000100003c20 (HPObjcTest`+[HPObject(Additions1) additions1ClassMethod])
}
(lldb) p $5.get(1).big()
(method_t::big) $7 = {
  name = "classMethod"
  types = 0x0000000100003f3f "v16@0:8"
  imp = 0x0000000100003c40 (HPObjcTest`+[HPObject classMethod])
}
(lldb) p $5.get(2).big()
(method_t::big) $8 = {
  name = "additions2ClassMethod"
  types = 0x0000000100003f3f "v16@0:8"
  imp = 0x0000000100003d40 (HPObjcTest`+[HPObject(additions2) additions2ClassMethod])
}
  • 通過(guò)類的isa找到元類金砍。
  • 元類的結(jié)構(gòu)也是objc_class局蚀,在元類中以同樣的方式獲取bitsdata()methods()就能獲取到類方法了。
  • 元類的作用就是存儲(chǔ)類方法恕稠。(在底層沒(méi)有所謂的類方法琅绅,都是對(duì)象方法。)

結(jié)論:類和分類的類方法存儲(chǔ)在元類中鹅巍。

至此千扶,屬性、方法和實(shí)例變量的結(jié)構(gòu)就清晰了骆捧,結(jié)構(gòu)如下圖:


objc_class結(jié)構(gòu)

總結(jié)

  • 屬性獲扰煨摺:類->bits(offset 0x20)->data()->properties().list.ptr.get(index)
  • 實(shí)例方法獲取:類->bits(offset 0x20)->data()->methods().list.ptr.get(index).big()
  • 成員變量獲攘参:類->bits(offset 0x20)->ro().ivars.get(index)
  • 類方法獲茸苯省: 類->isa->bits(offset 0x20)->data()->methods().list.ptr.get(index).big()

四蒲犬、 __has_feature(ptrauth_calls)

在源碼分析中經(jīng)车讼Γ看到__has_feature(ptrauth_calls),那么它究竟有什么作用呢恕洲?

  • __has_feature:判斷編譯器是否支持某個(gè)功能
  • ptrauth_calls:指針身份驗(yàn)證来涨,針對(duì)arm64e架構(gòu)图焰;使用Apple A12或更高版本A系列處理器的設(shè)備(iPhoneX以后,不包括X)支持arm64e架構(gòu)蹦掐。
    也就是iPhone X以上的設(shè)備支持指針驗(yàn)證楞泼。

Devices using the Apple A12 or later A-series processor — like the iPhone XS, iPhone XS Max, and iPhone XR — support the arm64e architecture. To test your adoption, you have to run your app on one of these devices. You can’t test using the Simulator.
參考鏈接
可以在Build Settings -> Architectures中配置:

image.png

PAC

這里就引出了一個(gè)問(wèn)題,什么是PAC笤闯?
PACPointer Authentication堕阔,它的目的即檢測(cè)和保護(hù)地址不被意外或惡意修改,使得應(yīng)用執(zhí)行更加安全颗味。PAC特性是由硬件提供的超陆,保護(hù)了函數(shù)調(diào)用期間,棧空間和地址的安全时呀。
具體可以參考:arm64e與PAC

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末张漂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谨娜,更是在濱河造成了極大的恐慌航攒,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趴梢,死亡現(xiàn)場(chǎng)離奇詭異漠畜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)坞靶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門憔狞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人彰阴,你說(shuō)我怎么就攤上這事瘾敢。” “怎么了尿这?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵簇抵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我射众,道長(zhǎng)正压,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任责球,我火速辦了婚禮焦履,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雏逾。我一直安慰自己嘉裤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布栖博。 她就那樣靜靜地躺著屑宠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仇让。 梳的紋絲不亂的頭發(fā)上典奉,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音丧叽,去河邊找鬼卫玖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛踊淳,可吹牛的內(nèi)容都是我干的假瞬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脱茉!你這毒婦竟也來(lái)了剪芥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤琴许,失蹤者是張志新(化名)和其女友劉穎税肪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體榜田,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡益兄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了串慰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唱蒸,死狀恐怖邦鲫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情神汹,我是刑警寧澤庆捺,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站屁魏,受9級(jí)特大地震影響滔以,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜氓拼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一你画、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桃漾,春花似錦坏匪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至恋追,卻和暖如春凭迹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苦囱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工嗅绸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撕彤。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓朽砰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞧柔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359