iOS类的结构分析

274 阅读9分钟

前言

本文基于objc4-781源码进行分析 首先我们创建一个Person对象个对象,里面有一些属性、实例方法以及类方法

案例

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;

- (void)sayHello;
+ (void)sayWord;
@end

@implementation Person

- (void)sayHello {
    NSLog(@"Hello, ");
}

+ (void)sayWord {
    NSLog(@"World!");
}

@end

我们通过lldb来调式一下玩玩

//首先打印一下objc 
(lldb) po objc
<Person: 0x10079b9b0>

//读取一下objc的内存情况
(lldb) x/4gx objc
0x10079b9b0: 0x001d8001000021cd 0x0000000000000000
0x10079b9c0: 0x697263534b575b2d 0x67617373654d7470

//拿到objc首地址
(lldb) p/x objc
(Person *) $2 = 0x000000010079b9b0

//根据之前的内容,通过首地址&0x00007ffffffffff8ULL,获取类的指针地址
(lldb) p/x  0x001d8001000021cd & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x00000001000021c8

//打印一下这个类,果然是Person
(lldb) po 0x00000001000021c8
Person

//读取0x00000001000021c8这个内存情况
(lldb) x 0x00000001000021c8
0x1000021c8: a0 21 00 00 01 00 00 00 40 41 33 00 01 00 00 00  .!......@A3.....
0x1000021d8: d0 61 64 00 01 00 00 00 07 00 00 00 1c 80 04 00  .ad.............

//通过调用 Person.class方式获取Person类
(lldb) x Person.class
0x1000021c8: a0 21 00 00 01 00 00 00 40 41 33 00 01 00 00 00  .!......@A3.....
0x1000021d8: d0 61 64 00 01 00 00 00 07 00 00 00 1c 80 04 00  .ad.............

//或者objc_getClass方式
(lldb) x object_getClass(objc)
0x1000021c8: a0 21 00 00 01 00 00 00 40 41 33 00 01 00 00 00  .!......@A3.....
0x1000021d8: d0 61 64 00 01 00 00 00 07 00 00 00 1c 80 04 00  .ad.............
发现以上三种方式读出来的内存信息是一样

//读取0x00000001000021c8的内存情况
(lldb) x/4gx  0x00000001000021c8
0x1000021c8: 0x00000001000021a0 0x0000000100334140
0x1000021d8: 0x00000001006461d0 0x0004801c00000007

//发现还是Person,这里已经是Person类的元类
(lldb) po 0x00000001000021a0
Person

//元类的指针地址&0x00007ffffffffff8ULL
(lldb) p/x 0x00000001000021a0 & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00000001000021a0

//发现还是Person
(lldb) po 0x00000001000021a0

//我们读取Person元类的内存信息
(lldb) x/4gx 0x00000001000021a0
0x1000021a0: 0x00000001003340f0 0x00000001003340f0
0x1000021b0: 0x00000001007a3a60 0x0004e03500000007

//打印元类的首地址
(lldb) po 0x00000001003340f0
NSObject

//把NSObject地址&0x00007ffffffffff8ULL得到存储类信息的地址
(lldb) p/x  0x00000001003340f0  & 0x00007ffffffffff8ULL
(unsigned long long) $16 = 0x00000001003340f0

打印 0x00000001003340f0,发现还是NSObject
(lldb) po 0x00000001003340f0
NSObject

//读取NSObject内存信息,发现首地址和NSObject一样
(lldb) x/4gx 0x00000001003340f0
0x1003340f0: 0x00000001003340f0 0x0000000100334140
0x100334100: 0x00000001007a3ba0 0x0004e03100000007

//打印首地址,还是NSObject
(lldb) po 0x00000001003340f0
NSObject

从这里我们可以看出上面验证的结果符合这个经典的isa走位图 )

从图中可以看出isa的走位

  • 对象 的 isa 指向 类(也可称为类对象)
  • 类 的 isa 指向 元类
  • 元类 的 isa 指向 根元类,即NSObject
  • 根元类 的 isa 指向 它自己

父子类继承关系的走位即Superclass的走位

  • 子类subClass继承自父类superClass
  • 父类superClass继承自 根类RootClass,根类是指NSObject
  • 根类NSObject继承自 nil

元类也存在继承,元类之间的继承关系如下

  • 子类的元类metal SubClass继承自父类的元类metal SuperClass
  • 父类的元类metal SuperClass继承自根元类Root metalClass
  • 根元类Root metal Class继承于根类RootClass,此时的根类是指NSObject

子类的实例Instance of Subclass和父类的实例Instance of Superclass 以及根类的实例Instance of Root class之间是没有任何关系

那么NSObject是有一个还是多个,我们来验证一下

 Class objc1 = [Person class];
 Class objc2 = [Person alloc].class;
 Class objc3 = object_getClass([Person alloc]);
 
 NSLog(@"\n%p-\n%p-\n%p", objc1, objc2, objc3);

打印结果如下:

0x1000021d8-
0x1000021d8-
0x1000021d8

这里可以验证类只会存在一份

关于objc_class与objc_object

之前的文章中我们知道 Class类型是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

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }
    .........

我们看到objc_class是继承objc_object,objc_class里面是没有isa,我们接着看objc_object

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;
	.........    
}

这里我看到了isa_t isa,这里我们可以得出,OC里面所有的Class都是以objc_class为模板创建的。这就是为什么所有的类都有isa,并且非常重要 早期的objc_class结构体和现在不一样,从源码里可以看到是这样的

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

但现在已经被废弃了,我们现在这个是开头就说的objc4-781最新优化的,我们后面的类的结构分析也是基于新版来分析。

类结构的探索

接下来我们通过lldb看看类的存储是怎么样的? 首先读取一下类的首地址

(lldb) p/x Person.class

打印输出

(Class) $2 = 0x00000001000021d8 Person

在读取一下类的内存信息

(lldb) x/4gx 0x00000001000021d8

打印结果

0x1000021d8: 0x00000001000021b0 0x0000000100334140
0x1000021e8: 0x0000000100669ff0 0x0002801c00000003

根据objc_class的源码,我们通过首地址偏移32字节找到bits,

//0x1000021d8+32 = 0x1000021f8
(lldb) p (class_data_bits_t *)0x1000021f8

打印结果

(class_data_bits_t *) $3 = 0x00000001000021f8

调用bits里面的data方法

(lldb) p $3->data()

得到class_rw_t 类型的data数据

(class_rw_t *) $5 = 0x0000000100669f90

打印data数据

(lldb) p *$5

结果如下

(class_rw_t) $6 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975664
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

在这里我们没有看到属性、方法等信息,接下来探索一下属性、方法,通过源码中我们看到class_rw_t中有相关方法 我们调用一下试试,上面我们已经拿到了class_rw_t类型的data

调用属性列表的方法

(lldb) p $6.properties()

打印结果

(const property_array_t) $7 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100002188
      arrayAndFlag = 4294975880
    }
  }
}

我们看到有个list,打印一下list

(lldb) p $7.list

输出

(property_list_t *const) $8 = 0x0000000100002188

拿到$8地址并打印

(lldb) p *$8

结果

(property_list_t) $9 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}

都这里我们已经看到属性name的相关信息了,读取数组的第一个元素

(lldb) p $9.get(0)

打印结果

(property_t) $10 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")

接着往下读取

(lldb) p $9.get(1)

提示错误,提示越界了

Assertion failed: (i < count), function get, file /Users/wupin/Desktop/Logic/大师班/20200909-大师班第3天-OC对象下-资料/01--课堂代码/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

接下来我们根据上面的方式在看看成员列表 首先打印下内存首地址

(lldb) p/x Person.class

输出

(Class) $0 = 0x0000000100002200 Person

偏移32字节找到bits的地址

(lldb) p (class_data_bits_t *) 0x0000000100002220

输出

(class_data_bits_t *) $1 = 0x0000000100002220

调用data方法

(lldb) p $1->data()

输出

(class_rw_t *) $2 = 0x0000000101944010

打印data数据

(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975664
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

获取bits中ro的地址

(lldb) p $2->ro()

输出

(const class_ro_t *) $5 = 0x00000001000020b0

获取ivars地址首地址

(lldb) p $5->ivars

输出

(const ivar_list_t *const) $7 = 0x0000000100002160

打印成员变量类别

(lldb) p *$7

输出结果可以看到happy

(const ivar_list_t) $8 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000021c8
      name = 0x0000000100000f4a "happy"
      type = 0x0000000100000f83 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

或者通过地址偏移也能找到带下划线的成员变量和自定义的成员变量

(lldb) p $8.get(0)
(ivar_t) $9 = {
  offset = 0x00000001000021c8
  name = 0x0000000100000f4a "happy"
  type = 0x0000000100000f83 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $8.get(1)
(ivar_t) $10 = {
  offset = 0x00000001000021d0
  name = 0x0000000100000f50 "_name"
  type = 0x0000000100000f83 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

我们在看下实例方法,先打印下

 p *$5

根据打印出的结构,有个baseMethodList

(const class_ro_t) $11 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000f40 "\x02"
  name = 0x0000000100000f39 "Person"
  baseMethodList = 0x00000001000020f8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002160
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000021a8
  _swiftMetadataInitializer_NEVER_USE = {}
}

找到baseMethodList首地址

(lldb) p $11.baseMethodList

输出

(method_list_t *const) $12 = 0x00000001000020f8

在读取下里面的内容

(lldb) p *$12

输出,sayHello是存储在这了

(method_list_t) $13 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100000f7b "v16@0:8"
      imp = 0x0000000100000d90 (KCObjc`-[Person sayHello])
    }
  }
}

这里可以看出通过{}定义的成员变量,会存储在类的bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量。通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性

那么类方法存储在哪里呢?根据前面的所了解的,类是元类的实例,subClass的isa指向元类,那我们元类中去找找

//打印首地址

(lldb) p/x Person.class

输出 (Class) $0 = 0x00000001000021d8 Person //读取内存信息

(lldb) x/4gx 0x00000001000021d8

输出

0x1000021d8: 0x00000001000021b0 0x0000000100334140
0x1000021e8: 0x000000010063f060 0x0002801c00000003

//获取元类的首地址

(lldb) p/x 0x00000001000021b0 & 0x00007ffffffffff8ULL

输出

(unsigned long long) $1 = 0x00000001000021b0

//偏移32位获取元类里面bits 地址

(lldb) p (class_data_bits_t *) 0x00000001000021d0

输出

(class_data_bits_t *) $2 = 0x00000001000021d0

//取元类里面bits数据地址

(lldb) p $2->data()

输出

(class_rw_t *) $4 = 0x000000010063efd0

打印bit数据

(lldb) p *$4

输出

(class_rw_t) $5 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975560
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff88436c60
}

//调用元类的方法列表

(lldb) p $5.methods()

输出

(const method_array_t) $8 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002090
      arrayAndFlag = 4294975632
    }
  }
}

//获取方法列表list指针地址

(lldb) p $8.list

输出

(method_list_t *const) $9 = 0x0000000100002090

//获取方法列表的数据,这里我们看到了sayWord这个类方法

(lldb) p *$9

输出

(method_list_t) $10 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayWord"
      types = 0x0000000100000f81 "v16@0:8"
      imp = 0x0000000100000d80 (KCObjc`+[Person sayWord])
    }
  }
}

//或者通过下面这种方式访问

(lldb) p $10.get(0)

输出

(method_t) $11 = {
  name = "sayWord"
  types = 0x0000000100000f81 "v16@0:8"
  imp = 0x0000000100000d80 (KCObjc`+[Person sayWord])
}

总结:

  • 类的实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表,例如Persong类的实例方法sayHello 就存储在 Person类的bits属性中,类中的方法列表除了包括实例方法,还包括属性的set方法 和 get方法

  • 类的类方法存储在元类的bits属性中,通过元类bits --> methods() --> list获取类方法列表,例如Person中的类方法sayWord 就存储在Person类的元类(名称也是Person)的bits属性中