objc底层:类的结构探索

588 阅读6分钟

Class结构总览

Class -> objc_class -> objc_object {
    // Class ISA;  8字节
    Class superclass;//formerly cache pointer and vtable
    cache_t cache; // class_rw_t * plus custom rr/alloc flags  
    class_data_bits_t bits;
    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
       bits.setData(newData);
    }
    ... 后面就不一一列出
}

上图可以看出Class是一个objc_object 的结构体,内部的data()函数返回的是一个class_rw_t的结构体:

"... 省略我们暂不关心的,具体代码请前往相关工程探索"
struct class_rw_t {
    ...
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ...
}

看到class_rw_t内的结构可以发现存储有方法属性等,而且有一个class_ro_t的结构体指针。

struct class_ro_t {
    ...
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    ...
}

class_ro_t 里边居然也有方法属性等参数,其实class_ro_t就是保存类的最初始数据ro=readonly,而class_rw_t=readwrite,ro中的数据不容修改。

接下来我们通过一个项目通过objc源码 objc源码分析 查看 来看看究竟Class内部结构的这些属性中到底存了哪些内容。

TestDemo

新建一个SSObject类继承~NSObject

#import <Foundation/Foundation.h>

@interface SSObject : NSObject
{
    NSString *_customValue;
}
@property (nonatomic, copy) NSString *cName;

- (void)sayHello;

+ (void)sayHappy;

@end
#import "SSObject.h"

@implementation SSObject

- (void)sayHello {
    
}

+ (void)sayHappy {
    
}
@end

main函数如下:

#import "SSObject.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SSObject *object = [[SSObject alloc] init];
        Class aClass = objc_getClass("SSObject");
        NSLog(@"~~~~~~~~~~~~~~~~%p",aClass);
    }
    return 0;
}

class_rw_t


运行项目我们在NSLog处做断点,通过lldb命令对内存进行打印:

(lldb) x/2gx aClass // 打印aClass的内存结构
0x100001248: 0x001d800100001221 0x0000000100b00140

(lldb) po 0x100001248 // 起始地址打印的是类
SSObject

(lldb) 0x100001248 + 32字节 = 0x100001268 -> class_data_bits_t 
此处为什么 "+32",我们再看objc_object的结构,
objc_object {
    // Class ISA;  8字节
    Class superclass; 8字节
    cache_t cache; 16字节
    class_data_bits_t bits;
} 可以看出+32的位置正好就是 class_data_bits_t 的首地址。

由于class_data_bits_t不是常规id等类型,我们需要强转得到:
(lldb) p (class_data_bits_t *)0x100001268
(class_data_bits_t *) $2 = 0x0000000100001268

我们想得到class_rw_t的内容,即通过data()的函数
class_rw_t *data() { 
        return bits.data();
    }
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101039360

(lldb) p *$3 // 打印指针内容
(class_rw_t) $4 = {
  flags = 2148139008
  version = 0
  "ro = 0x00000001000011b0"
  methods = {
    ...
  }
  properties = {
    ...
  }
  protocols = {
    ...
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000100000ef6 "SSObject"
}
$4 即是 "class_rw_t" 其内部的"ro" 即是"class_ro_t"

class_ro_t

(lldb) p $4.ro
(const class_ro_t *) $16 = 0x00000001000011b0
(lldb) p *$16
(const class_ro_t) $17 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000eff "\x02"
  name = 0x0000000100000ef6 "SSObject"
  "baseMethodList = 0x00000001000010e8"
  baseProtocols = 0x0000000000000000
  "ivars = 0x0000000100001150"
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100001198
  _swiftMetadataInitializer_NEVER_USE = {}
}

属性 & 成员变量

我们对class_rw_tclass_ro_t中的属性和成员变量进行探索:

(lldb) p $4.properties
(property_array_t) $12 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100001198
      arrayAndFlag = 4294971800
    }
  }
}
(lldb) p $12.list "打印所有属性列表"
(property_list_t *) $13 = 0x0000000100001198
(lldb) p *$13
(property_list_t) $14 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    "属性"
    first = (name = "cName", attributes = "T@"NSString",C,N,V_cName")
  }
}

通过结果我们可以看到属性cName确实存放在class_rw_t中,但是我们并没有看到类似成员变量的属性,我们接着去看上述class_ro_t, 我们看到类似ivars的字段,我们看看里边究竟有哪些内容。

(lldb) p $17.ivars
(const ivar_list_t *const) $18 = 0x0000000100001150
(lldb) p *$18
(const ivar_list_t) $19 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    "count = 2"
    first = {
      offset = 0x0000000100001218
      name = 0x0000000100000f31 "_customValue"
      type = 0x0000000100000f6b "@"NSString""
      alignment_raw = 3
      size = 8
    }
  }
}

Note:此处我们确实看到我们定义在头文件中的NSString *_customValue; 但是我们只定义了一个成员变量,为何此处count=2,打印一下即可:

(lldb) p $19.get(1)
(ivar_t) $20 = {
  offset = 0x0000000100001210
  name = 0x0000000100000f3e "_cName"
  type = 0x0000000100000f6b "@"NSString""
  alignment_raw = 3
  size = 8
}

Why: 原来是我们属性cName自动生成的带下划线的成员变量,那么我们属性和成员变量告一段落,接下来我们看看方法。

实例方法

(lldb) p $17.baseMethodList
(method_list_t *const) $21 = 0x00000001000010e8
(lldb) p *$21
(method_list_t) $22 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100000f50 "v16@0:8"
      imp = 0x0000000100000cf0 (ClassTest`-[SSObject sayHello] at SSObject.m:12)
    }
  }
}

诚然,我们确实看到方法列表了,但是我们貌似只定义了- (void)sayHello;+ (void)sayHappy 方法,为何此处count=4,打印便知:

(lldb) p $22.get(1)
(method_t) $23 = {
  name = "cName"
  types = 0x0000000100000f58 "@16@0:8"
  imp = 0x0000000100000d10 (ClassTest`-[SSObject cName] at SSObject.h:16)
}
(lldb) p $22.get(2)
(method_t) $24 = {
  name = "setCName:"
  types = 0x0000000100000f60 "v24@0:8@16"
  imp = 0x0000000100000d40 (ClassTest`-[SSObject setCName:] at SSObject.h:16)
}
(lldb) p $22.get(3)
(method_t) $25 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f50 "v16@0:8"
  imp = 0x0000000100000d80 (ClassTest`-[SSObject .cxx_destruct] at SSObject.m:10)
}

原来:我们定义属性系统会自动帮我们生成setget方法,还有一个cxx_destruct的c++方法是系统默认添加的,这样加上first里边的sayHello方法正好是4个,那么问题来了,我们的+ (void)sayHappy的方法呢?

类方法

之前我们了解过isa的相关知识,类方法保存在meta-class中,此处暂时仅以上帝视角去验证:

(lldb) p 0x001d800100001221 & 0x00007ffffffffff8
(long) $27 = 4294971936 "元类"
(lldb) x/2gx $27
0x100001220: 0x001d800100b000f1 0x0000000100b000f0
(lldb) p (class_data_bits_t *)0x100001240
(class_data_bits_t *) $28 = 0x0000000100001240
(lldb) p $28->data()
(class_rw_t *) $29 = 0x00000001010647b0
(lldb) p *$29
(class_rw_t) $30 = {
  flags = 2685075456
  version = 7
  ro = 0x00000001000010a0
  methods = {
   ...
  }
  properties = {
    ...
  }
  protocols = {
    ...
  }
  ...
}
(lldb) p $30.ro
(const class_ro_t *) $31 = 0x00000001000010a0
(lldb) p *$31
(const class_ro_t) $32 = {
  flags = 389
  instanceStart = 40
  instanceSize = 40
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000ef6 "SSObject"
  baseMethodList = 0x0000000100001080
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000000000000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $32.baseMethodList
(method_list_t *const) $33 = 0x0000000100001080
(lldb) p *$33
(method_list_t) $34 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100000f50 "v16@0:8"
      imp = 0x0000000100000d00 (ClassTest`+[SSObject sayHappy] at SSObject.m:16)
    }
  }
}

Okay如此我们确实找到了+ (void)sayHappy方法所在。

总结

综上所述,我们分析并证实:

1.属性会自动生成带`_` 的成员变量。
2.如果我们定义了属性,系统会自动帮我们生成set和get方法,并保存在类中。
3.静态方法没有保存在类中,而是元类中。

今天我们就探讨到这,像其中的protocol 等大家可以继续探索,有任何问题欢迎指正。