IOS 类的结构分析上

320 阅读13分钟

前言

上一篇博客对象的本质与isa,我们初探了类的结构与isa,知道了isa其实是指向struct objc_class的结构体指针,最后也初步认识了苹果官方提供的类的继承与走位图,今天我们就深入探索一下类的底层结构然后深刻理解一下苹果官方的isa走位图。

1.0 内存偏移

在探索类的底层结构之前先了解一下指针与偏移量。指针就是一个存放地址的变量,这个变量也需要内存地址存储。偏移量是相对首地址而言的,首地址就是内存中对象或变量的地址。

1.0.1 普通指针

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        int b = 10;
        int* c=&a;
        int* d=&b;
        NSLog(@"%d--%p--%p--%p",a,c,&a,&c);
        NSLog(@"%d--%p--%p--%p",b,d,&b,&d);
    }
    return 0;
}

输出如下:

10--0x7ffeefbff1ec--0x7ffeefbff1ec--0x7ffeefbff1e0
10--0x7ffeefbff1e8--0x7ffeefbff1e8--0x7ffeefbff1d8

分析:

  • a的值是10,地址是0x7ffeefbff1ec也是c的值,b的值是10,地址是0x7ffeefbff1e8也是d的值,说明虽然值相同,但是都会分配内存去分别存储,a的地址和b的地址相差0x4即4个字节,相差的值与a的类型有关,a是int类型,占4个字节。
  • c的值是0x7ffeefbff1ec,地址是0x7ffeefbff1e0,d的值是0x7ffeefbff1e8,地址是0x7ffeefbff1d8,c的地址和d的地址相差0x8即8个字节,c是指针,指针占8个字节
  • a、b、c、d 都是变量,变量存储在栈区栈区的地址是由高地址到低地址,a地址0x7ffeefbff1ec大于b的地址0x7ffeefbff1e8。

1.0.2 对象指针

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       LGPerson *p1 = [LGPerson alloc];
       LGPerson *p2 = [LGPerson alloc];
       NSLog(@"%@ -- %p",p1,&p1);
       NSLog(@"%@ -- %p",p2,&p2);
    }
    return 0;
}

输出如下:

<LGPerson: 0x100626410> -- 0x7ffeefbff1e8
<LGPerson: 0x100625440> -- 0x7ffeefbff1e0

分析:

  • 对象的内存在堆区,局部变量的内存在栈区
  • 堆区地址是低地址到高地址栈区的地址是高地址到低地址。p1指针的地址0x7ffeefbff1e8大于p2指针的地址0x7ffeefbff1e0指针是存放地址的变量,这个变量在栈区,即指针存放在栈区。p1的值0x100626410即p1对象的地址 小于 p2的值0x100625440即p2对象的地址,对象是存放在堆区的。

图解如下

截屏2021-06-27 下午4.43.10.png

1.0.3 数组指针

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int c[4] = {1,2,3,4};
        int *d   = c;
        NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
        NSLog(@"%p - %p - %p",d,d+1,d+2);

        for (int i = 0; i<4; i++) {
            int value =  *(d+i);
            NSLog(@"%d",value);
        }
    }
    return 0;
}

输出如下:

0x7ffeefbff1e0 - 0x7ffeefbff1e0 - 0x7ffeefbff1e4
0x7ffeefbff1e0 - 0x7ffeefbff1e4 - 0x7ffeefbff1e8
1
2
3
4

分析如下:

  • 数组的地址就是数组元素中的首地址,即&c&c[0]
  • 数组中每个元素的偏移量,根据当前元素的数据类型决定的
  • 数组的元素地址可以通过首地址+n*类型大小方式,这种方式是数组中的元素类型必须相同

总结:

  • 变量的内存在栈区对象的内存在堆区指针是一个存放地址的变量,在栈区
  • 栈区高地址到低地址,堆区低地址到高地址。
  • 内存地址就是内存元素的首地址
  • 内存偏移可以根据首地址+ 偏移值方法获取相对应变量的地址。

2.0 isa的分析

上一篇博客对象的本质与isa我们分析isa获取isa中shiftcls的值,即类指针的值,从而获取isa关联的类对象。我们发现实例对象的isa关联了类对象,而类对象的isa又关联了一个类对象,这个类对象叫做元类,下面我们具体探讨下。

2.0.1 类与元类

  Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    Class class4 = [LGPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);

输出:

0x100008360-
0x100008360-
0x100008360-
0x100008360

分析:无论alloc多少次,类对象的内存地址都相同,说明内存中只有一块内存存储这个类对象

我们写个demo,LGPerson *p = [LGPerson alloc]通过lldb探索一下:

(lldb) x/4gx p
0x1007055f0: 0x011d800100008365 0x0000000000000000
0x100705600: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008360
(lldb) po 0x0000000100008360
LGPerson

(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff80691008
0x100008370: 0x0000000101805860 0x0002802c00000003
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100008338
(lldb) po 0x0000000100008338
LGPerson

分析:对象p的isa关联类对象LGPerson,即地址0x0000000100008360,但是0x0000000100008338地址也是LGPerson?上面我们知道内存中只有一块内存存储这个类对象,那么这个0x0000000100008338是什么呢?苹果官方称它为元类,系统自动生成的。

那么这个元类的isa关联的类是什么,后面到底关联了几层对象?接着上面的lldb继续探索

(lldb) x/4gx p
0x1007055f0: 0x011d800100008365 0x0000000000000000
0x100705600: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008360
(lldb) po 0x0000000100008360
LGPerson

(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff80691008
0x100008370: 0x0000000101805860 0x0002802c00000003
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100008338
(lldb) po 0x0000000100008338
LGPerson

(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff80690fe0 0x00007fff80690fe0
0x100008348: 0x000000010053a640 0x0002e03500000003
(lldb) p/x 0x00007fff80690fe0 &  0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00007fff80690fe0
(lldb) po 0x00007fff80690fe0
NSObject

(lldb) x/4gx 0x00007fff80690fe0
0x7fff80690fe0: 0x00007fff80690fe0 0x00007fff80691008
0x7fff80690ff0: 0x0000000101807b80 0x0003e03100000007
(lldb) p/x 0x00007fff80690fe0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00007fff80690fe0

分析:LGPerson对象的isa-->LGPerson类的isa-->LGPerson元类的isa-->NSObject根元类

既然最后指向的根源类NSObject,那么就再看下NSObject对象的isa走位图吧。NSObject* myobjc=[NSObject alloc] lldb调试如下

(lldb) x/4gx myobjc
0x103098400: 0x011dffff80691009 0x0000000000000000
0x103098410: 0x63756f54534e5b2d 0x7765695672614268
(lldb) p/x 0x011dffff80691009 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00007fff80691008
(lldb) po 0x00007fff80691008
NSObject

(lldb) x/4gx 0x00007fff80691008
0x7fff80691008: 0x00007fff80690fe0 0x0000000000000000
0x7fff80691018: 0x000000010052a160 0x0001801000000003
(lldb) p/x 0x00007fff80690fe0 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x00007fff80690fe0
(lldb) po 0x00007fff80690fe0
NSObject

(lldb) x/4gx 0x00007fff80690fe0
0x7fff80690fe0: 0x00007fff80690fe0 0x00007fff80691008
0x7fff80690ff0: 0x0000000103007d90 0x0003e03100000007
(lldb) p/x 0x00007fff80690fe0 & 0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00007fff80690fe0
(lldb) po 0x00007fff80690fe0
NSObject

分析:NSObject对象的isa-->NSObject类的isa-->NSObject元类isa-->NSObject元类自己即根元类

对比LGPerson我们上个图解更容易理解上面的分析。

截屏2021-06-27 下午8.49.01.png

2.0.2 类的继承

上面我们分析了类的isa关联关系,但是继承类的isa关联关系也是这样的吗,继承类的父类的isa又是怎么关联的呢?我们新建一个对象LGTeacher *t = [LGTeacher alloc];LGTeacher继承于类LGPerson,lldb调试一下isa关联关系。

(lldb) x/4gx t
0x103836e70: 0x011d800100008315 0x0000000000000000
0x103836e80: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008315 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x0000000100008310
(lldb) po 0x0000000100008310
LGTeacher

(lldb) x/4gx 0x0000000100008310
0x100008310: 0x00000001000082e8 0x0000000100008360
0x100008320: 0x00007fff20282aa0 0x0000802c00000000
(lldb) p/x 0x00000001000082e8 & 0x00007ffffffffff8ULL
(unsigned long long) $10 = 0x00000001000082e8
(lldb) po 0x00000001000082e8
LGTeacher

(lldb) x/4gx 0x00000001000082e8
0x1000082e8: 0x00007fff80690fe0 0x0000000100008338
0x1000082f8: 0x000000010061cf20 0x0002e03500000003
(lldb) p/x 0x00007fff80690fe0 & 0x00007ffffffffff8ULL
(unsigned long long) $12 = 0x00007fff80690fe0
(lldb) po 0x00007fff80690fe0
NSObject

分析::LGTeacher对象的isa-->LGTeacher类的isa-->LGTeacher元类的isa-->NSObject根元类。这样看跟LGTeacher的isa和LGPerson的isa走向是一样的,难道LGTeacher和LGPerson就没有其他关系了吗?

继续demo探索:

     // LGPerson元类
    Class pMetaClass = object_getClass(LGPerson.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"元类:%@ - %p 元类父类:%@ - %p",pMetaClass,pMetaClass,psuperClass,psuperClass);
    
    //LGTeacher 元类
    Class tMetaClass = object_getClass(LGTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"元类:%@ - %p 元类父类:%@ - %p",tMetaClass,tMetaClass,tsuperClass,tsuperClass);
    
    // NSObject根元类父类
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"%@ - %p",nsuperClass,nsuperClass);

输出如下:

元类:LGPerson - 0x100008338 元类父类:NSObject - 0x7fff80690fe0
元类:LGTeacher - 0x1000082e8 元类父类:LGPerson - 0x100008338
(null) - 0x0

分析: LGTeacher元类的父类地址是0x100008338,LGPerson元类的地址是也是0x100008338,LGTeacher元类继承于LGPerson元类,NSObject根元类继承于nil

继承关系isa如下图:

截屏2021-06-27 下午8.45.55.png

通过上面的分析对比下苹果官方的isa,是不是就比较清晰了:

isa流程图.png

3.0 类的结构分析

上一篇博客对象的本质与isa通过底层源码编译发现对象的本质是objc_object结构体,而类的本质是objc_class结构,isa是一个指向objc_class的结构体指针,那么就分析一下类的结构。查找源码objc4-818.2,发现objc_class结构是这样的:

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
    //下面是一些方法省略
};
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

分析:objc_class继承objc_object,objc_object里面只有一个成员变量isa。那么objc_class中的成员变量是isa、superclass、cache、bits。superclass是一个指向objc_class的结构体指针8个字节。

源码找到cache_t的定义:

typedef unsigned long           uintptr_t;

#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;   // 4
#if __LP64__
            uint16_t                   _flags;       // 2
#endif
            uint16_t                   _occupied;    // 2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
    };
    
    //下面是一些方法省略
};

分析:

  • cache_t 是结构体类型,有两个成员变量_bucketsAndMaybeMask和一个联合体。
  • _bucketsAndMaybeMask是 uintptr_t无符长整型占8字节
  • 联合体里面有两个成员变量结构体 _originalPreoptCache,联合体的内存大小由成员变量中的最大变量类型决定。 - _originalPreoptCache 是结构体指针占8字节。结构体中有_maybeMask,_flags,_occupied。 _maybeMask是uint32_t占 4字节,_flags和_occupied是uint16_t 各占2字节,结构体大小是8字节。
  • cache_t的内存大小是 8+8 或者是8+4+2+2都是16字节。

objc_class总结

  • 结构体中第一个元素的地址即为这个结构体的首地址
  • isa的内存地址是objc_class的首地址,占8个字节
  • superclass的内存地址是首地址+0x8,占8个字节
  • cache的内存地址是首地址+0x10,占16个字节
  • bits的内存地址是首地址+0x20

3.1 bit

今天重点分析一下bit中存储了什么,注意我们一直在分析的是类,bit是在类中的。源码查看bits是class_data_bits_t结构体,结构体如下,源码过长,重点看一下主要的data():

struct class_data_bits_t {
    friend objc_class;//friend关键字把objc_class设为了友元结构体
    // Values are the FAST_ flags above.
    uintptr_t bits;//无符号长整形 8字节
private:
   //省略一些结构体方法
public:
   //data 存储属性、方法、协议等
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    //存储成员变量
    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;
    }
}
  //省略一些结构体方法 
};

分析:

  • class_data_bits_t结构体中只有一个长整形成员变量bits
  • 长整形变量bits并没有探索的欲望,但是发现一个指向class_rw_t的结构体指针data(),程序员的直觉data里应该存储着什么。

看一下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;
    //省略...
    class_rw_ext_t *ext() const {
        //省略...
    }
    class_rw_ext_t *extAllocIfNeeded() {
       //省略...
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }
    //成员变量
    const class_ro_t *ro() const {
        //省略...
    }

    void set_ro(const class_ro_t *ro) {
        //省略...
    }
    //方法
    const method_array_t methods() const {
     //省略...
    }

    const property_array_t properties() const {
         //省略...
    }

    const protocol_array_t protocols() const {
    //省略...
    }

分析:

  • class_rw_t结构体中有我们熟悉的methods(),properties(),protocols()

3.2 属性获取

实例验证一下class_rw_t是否存储着方法、属性、协议。写个demo如下,实例化一下LGPerson demo如下:

@protocol LGPersonDegete<NSObject>

-(void)testLGperson;

@end
@interface LGPerson : NSObject<LGPersonDegete>{
    NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;

- (void)sayNB;
+ (void)say666;
@end

lldb调试获取属性:

KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008380 LGPerson
(lldb) p/x 0x0000000100008380 + 0x20  //注释:首地址+0x20偏移量为class_data_bits_t地址
(long) $1 = 0x00000001000083a0
(lldb) p (class_data_bits_t*)$1
(class_data_bits_t *) $2 = 0x00000001000083a0
(lldb) p $2->data()                    //注释:获取data
(class_rw_t *) $3 = 0x0000000100723c30
(lldb) p $3->properties()             //注释:获取data中属性
(const property_array_t) $4 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008260
      }
      arrayAndFlag = 4295000672
    }
  }
}
(lldb) p $4.list                     //注释:获取属性列表
(const RawPtr<property_list_t>) $5 = {
  ptr = 0x0000000100008260
}
(lldb) p $5.ptr                      //注释:获取属性列表ptr
(property_list_t *const) $6 = 0x0000000100008260
(lldb) p *$6
(property_list_t) $7 = {             //注释:获取属性列表信息
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $7.get(0)                   //注释:第一个属性
(property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb)  p $7.get(1)                  //注释:第二个属性
(property_t) $9 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
(lldb) 

分析:注释标注了lldb调试的过程。LGPerson->class_data_bits_t->data->properties->list->ptr->get

3.3 方法获取

lldb调试如下:

(lldb) p $3->methods()              //注释:获取方法
(const method_array_t) $10 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008160
      }
      arrayAndFlag = 4295000416
    }
  }
}
(lldb) p $10.list                  //注释:获取方法列表
(const method_list_t_authed_ptr<method_list_t>) $11 = {
  ptr = 0x0000000100008160
}
(lldb) p $11.ptr                  //注释:获取方法列表ptr
(method_list_t *const) $12 = 0x0000000100008160
(lldb) p *$12
(method_list_t) $13 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $13.get(0).big()        //注释:获取第一个方法
(method_t::big) $15 = {
  name = "sayNB"
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
}
(lldb) p $13.get(1).big()       //注释:获取第二个方法
(method_t::big) $16 = {
  name = "hobby"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003db0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $13.get(2).big()
(method_t::big) $17 = {
  name = "setHobby:"
  types = 0x0000000100003f8b "v24@0:8@16"
  imp = 0x0000000100003de0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $13.get(3).big()
(method_t::big) $18 = {
  name = "init"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003ce0 (KCObjcBuild`-[LGPerson init])
}

分析:

  • 跟获取properties()不一样的是,获取methods实际上是获取的method_t,method_t中有个big()方法可以获取到method。
  • 注释标注了lldb调试的过程。LGPerson->class_data_bits_t->data->methods()->list->ptr->get(0).big()
  • 仔细看LGPerson,我定义了两个方法sayNB和say666,通过lldb调试发现LGPerson.class类中只有sayNB()方法而没有say666()方法,why?sayNB()是实例方法它在类中,那么对象方法say666()在哪里呢?关注我下一篇博客详细分析。

3.4 协议获取

(lldb) p/x LGPerson.class
(Class) $15 = 0x0000000100008898 LGPerson
(lldb) p/x 0x0000000100008898 + 0x20
(long) $16 = 0x00000001000088b8
(lldb) p/x (class_data_bits_t*)$16
(class_data_bits_t *) $17 = 0x00000001000088b8
(lldb) p $17->data()
(class_rw_t *) $18 = 0x0000000100707990
(lldb) p $18->protocols()
(const protocol_array_t) $19 = {
  list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008348
      }
      arrayAndFlag = 4295000904
    }
  }
}
(lldb) p $19.list
(const RawPtr<protocol_list_t>) $20 = {
  ptr = 0x0000000100008348
}
(lldb) p $20.ptr
(protocol_list_t *const) $21 = 0x0000000100008348
(lldb) p *$21
(protocol_list_t) $22 = (count = 1, list = protocol_ref_t [] @ 0x00007fa105c5eae8)
(lldb) p $22.list[0]       //查看源码发现protocol_list_t结构体有个list[0]
(protocol_ref_t) $23 = 4295002352
(lldb) p (protocol_t*)$23       //protocol_t强转protocol_ref_t
(protocol_t *) $24 = 0x00000001000088f0
(lldb) p $24
(protocol_t *) $24 = 0x00000001000088f0
(lldb) p *$24
(protocol_t) $25 = {
  objc_object = {
    isa = {
      bits = 4298547400
      cls = Protocol
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 537318425
        magic = 0
        weakly_referenced = 0
        unused = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  mangledName = 0x0000000100003c3a "LGPersonDegete"
  protocols = 0x0000000100008430
  instanceMethods = 0x0000000100008448
  classMethods = 0x0000000000000000
  optionalInstanceMethods = 0x0000000000000000
  optionalClassMethods = 0x0000000000000000
  instanceProperties = 0x0000000000000000
  size = 96
  flags = 0
  _extendedMethodTypes = 0x0000000100008468
  _demangledName = 0x0000000000000000
  _classProperties = 0x0000000000000000
}
(lldb) 

分析:

  • protocol的获取要复杂一下需要结构源码分析,先获取到结构体protocol_list_t,在结构protocol_list_t中获取成员变量list[0],成员变量是protocol_ref_t,强制转换protocol_ref_tprotocol_t,再取protocol_t值
  • LGPerson->class_data_bits_t->data->protocol()->list->ptr->list[0]->protocol_t*强转

3.5 成员变量的获取

class_rw_t确实能获取到方法、属性以及协议,但是发现没有,我们demo中的成员变量subject并没有获取到,它存储在哪里呢?在class_rw_t中有个获取ro()方法,lldb探索一下。

(lldb) p/x LGPerson.class
(Class) $27 = 0x0000000100008898 LGPerson
(lldb) p/x 0x0000000100008898 + 0x20
(long) $28 = 0x00000001000088b8
(lldb) p (class_data_bits_t*)$28
(class_data_bits_t *) $29 = 0x00000001000088b8
(lldb) p $29->data()
(class_rw_t *) $30 = 0x0000000100707990
(lldb) p $30->ro()
(const class_ro_t *) $31 = 0x0000000100008250
(lldb) p *$31
(const class_ro_t) $32 = {
  flags = 0
  instanceStart = 8
  instanceSize = 32
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "LGPerson" {
      Value = 0x0000000100003c31 "LGPerson"
    }
  }
  baseMethodList = 0x0000000100008298
  baseProtocols = 0x0000000100008348
  ivars = 0x0000000100008360
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000083c8
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $32.ivars
(const ivar_list_t *const) $33 = 0x0000000100008360
(lldb) p *$33
(const ivar_list_t) $34 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $34.get(0)
(ivar_t) $35 = {
  offset = 0x00000001000087e0
  name = 0x0000000100003caf "subject"
  type = 0x0000000100003e0d "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) 

分析:

  • 成员变量subject在ro()
  • LGPerson->class_data_bits_t->data->ro()->ivars->get(0)

总结:

  • isa是一个指向objc_class结构体指针objc_class继承objc_object
  • objc_class结构体有成员变量isa、superclass、cache、bits
  • bits是class_data_bits_t结构体,结构体指针方法data()可以获取属性列表、方法列表、成员变量列表、协议列表等。
  • 成员变量在data()结构体指针的ro()