iOS底层原理探索-类的结构分析

在『iOS底层原理探索-isa结构&指向分析』中,我们已经知道:

本文将对类的结构进一步的分析,同样的,先提出几个问题:

  • objc_classobjc_object 有什么关系?
  • 成员变量和属性有什么区别?
  • 实例方法与类方法的归属问题?

类的本质

类的本质是 objc_class 类型的结构体,objc_class 继承与 objc_object

我们再来看一下:NSObject的定义

typedef struct objc_class *Class;
typedef struct objc_object *id;

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

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();
    }
    ...
};

objc_object的定义

struct objc_object {
  private:
      isa_t isa;
  }
}

仔细比较 NSObjectobjc_object,两者非常的相似,可以总结为:

  • NSObjectobjc_object 的仿写,和 objc_object定义是一样的,在底层会被编译成 objc_object
  • 所以,NSObject类是 Objective-C 的 objc_object

类的结构

objc_class 中定义,结构体有 4 个成员变量:isa、superclass、cache、bits

Class ISA

这是继承于 objc_object 而来的,指向的是当前的类,占 8 字节

Class superclass

父类,superclass是 Class 类型,占 8 字节

cache_t cache

cache_t的定义

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

正如所见的,cache_t 是一个结构体,内存长度由所有元素决定:

  • _buckets | _maskAndBucketsbucket_t*是结构体指针,uintptr_t也是指针,8 字节
  • _mask | _mask_unusedmask_tint 类型,占 4 个字节
  • _flagsuint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
  • _occupieduint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节

因此,cache_t 占用 16 字节

关于 cache_t会在后续进行详细展开说明

class_data_bits_t bits

class_data_bits_t的定义

struct class_data_bits_t {
    friend objc_class;
  
    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
  bool getBit(uintptr_t bit) const
  {
      return bits & bit;
  }
  ...
public:

  class_rw_t* data() const {
      return (class_rw_t *)(bits & FAST_DATA_MASK);
  }
  ...
}

class_rw_t的定义

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    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;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
}

class_rw_ext_t的定义

struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

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;
}

image-20200916164750486

在 objc_class 结构体中的注释写到 class_data_bits_t相当于 class_rw_t指针加上 rr/alloc 的标志。

同时,也向外面提供了便捷方法用于返回其中的 class_rw_t * 指针

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

类的属性、方法、协议

Objc 类的属性、方法、以及遵循的协议都放在 class_rw_t 中,class_ro_t是一个指向常量的指针,存储由编译器决定的属性、方法和遵守的协议。rw-readwrite, ro-readonly

在编译期

类的结构中的 class_data_bits_t *data 指向的是一个 class_ro_t *指针

在运行时

调用 realizeClassWithoutSwift 方法,会做一下 3 件事情:

  1. class_data_bits_t调用 data 方法,将结果从 class_rw_t 强制转换为 class_ro_t 指针
  2. 初始化一个 class_rw_t 结构体
  3. 设置结构体 ro 的值以及 flag

最后,调用 methodizeClass 方法,将类中的属性、协议、方法都加载进来。

查看属性、方法的分布

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

+ (void)walk;
- (void)run;
@end

@implementation Person
+ (void)walk {};
- (void)run {};
@end

上面我们已经知道了,isabits的内存偏移为 32 位,即bits的地址是类的内存首地址+isa、superclass、cache 的内存长度

(lldb) p/x Person.class
(Class) $1 = 0x0000000100003228 Person
(lldb) p (class_data_bits_t *)0x0000000100003248
(class_data_bits_t *) $2 = 0x0000000100003248

根据data()方法,获取 class_rw_t

(lldb) p $2->data()
(class_rw_t *) $4 = 0x0000000101c46110
(lldb) p *$4
(class_rw_t) $5 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294979784
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

781 的源码和之前的源码不太一样,这里并不能直接读取到 methods、properties、protocols

类的属性

(lldb) p $5.properties()
(const property_array_t) $13 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000031c0
      arrayAndFlag = 4294980032
    }
  }
}
(lldb) p $13.list
(property_list_t *const) $14 = 0x00000001000031c0
(lldb) p *$14
(property_list_t) $15 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}

(lldb) p $15.get(0)
(property_t) $16 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")

通过上面的方式,就可以拿到所想要 property_t 属性列表,其中 count 是指总共的属性数量。

之前,分析了成员变量存储在 class_ro_t 中,我们也来打印一下看看

(lldb) p $5.ro()
(const class_ro_t *) $18 = 0x00000001000030c8
(lldb) p *$18
(const class_ro_t) $19 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001e6f "\x02"
  name = 0x0000000100001e68 "Person"
  baseMethodList = 0x0000000100003110
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100003178
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000031c0
  _swiftMetadataInitializer_NEVER_USE = {}
}

接着,打印 ivars

(lldb) p $19.ivars
(const ivar_list_t *const) $20 = 0x0000000100003178
(lldb) p *$20
(const ivar_list_t) $21 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000031f0
      name = 0x0000000100001e76 "nickName"
      type = 0x0000000100001ebe "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

同样的,输出每一个ivar_list的每一个元素

(lldb) p $21.get(0)
(ivar_t) $22 = {
  offset = 0x00000001000031f0
  name = 0x0000000100001e76 "nickName"
  type = 0x0000000100001ebe "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $21.get(1)
(ivar_t) $23 = {
  offset = 0x00000001000031f8
  name = 0x0000000100001e7f "_name"
  type = 0x0000000100001ebe "@\"NSString\""
  alignment_raw = 3
  size = 8
}

除了成员变量 nickName 之外,编译器会在底层自动将属性生成一个成员变量(前缀_+属性名)

类的方法

查看源码,关于 class_rw_t 部分,知道通过methods()、properties()、protocols()分别获取方法、属性、协议

(lldb) p $5.methods()
(const method_array_t) $6 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100003110
      arrayAndFlag = 4294979856
    }
  }
}

(lldb) p $6.list
(method_list_t *const) $7 = 0x0000000100003110
(lldb) p $7
(method_list_t *const) $7 = 0x0000000100003110
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = ".cxx_destruct"
      types = 0x0000000100001eb6 "v16@0:8"
      imp = 0x0000000100001970 (ObjcTest`-[Person .cxx_destruct] at main.m:29)
    }
  }
}

通过上面的方式,就可以拿到所想要 method_list 方法列表,其中 count 是指总共的方法数量,分别打印一下

(lldb) p $8.get(0)
(method_t) $9 = {
  name = ".cxx_destruct"
  types = 0x0000000100001eb6 "v16@0:8"
  imp = 0x0000000100001970 (ObjcTest`-[Person .cxx_destruct] at main.m:29)
}
(lldb) p $8.get(1)
(method_t) $10 = {
  name = "name"
  types = 0x0000000100001eca "@16@0:8"
  imp = 0x0000000100001910 (ObjcTest`-[Person name] at main.m:16)
}
(lldb) p $8.get(2)
(method_t) $11 = {
  name = "setName:"
  types = 0x0000000100001ed2 "v24@0:8@16"
  imp = 0x0000000100001940 (ObjcTest`-[Person setName:] at main.m:16)
}
(lldb) p $8.get(3)
(method_t) $12 = {
  name = "run"
  types = 0x0000000100001eb6 "v16@0:8"
  imp = 0x0000000100001900 (ObjcTest`-[Person run] at main.m:31)
}

方法method_t的定义

struct method_t {
    SEL name;
    const char *types;
    IMP 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; }
    };
};

里面包含3个成员变量。

  • SEL是方法的名字name
  • types是Type Encoding类型编码,类型可参考Type Encoding,在此不细说
  • IMP是一个函数指针,指向的是函数的具体实现

在runtime中消息传递和转发的目的就是为了找到IMP,并执行函数

系统在底层会添加 c++的.cxx_destruct方法,同时编译器还为属性生成了一个 setter 方法和 getter 方法,现在会有一个疑问:Person的类方法 walk 去哪里了?

在上面,已经有介绍关于类和元类的概念,那么对于类方法的归属:类方法可以理解成元类实例方法,因此,类方法存储元类

总结

  • 成员变量存放在ivar
  • 属性存放在property,同时也会存一份在ivar,并生成settergetter方法
  • 实例方法存放在里面methods
  • 类方法存放在元类里面methods

参考资料:

深入解析 ObjC 中方法的结构