iOS进阶之路 (四)类的原理

589 阅读6分钟

前面几篇文章中,我们学习了对象的创建、内存布局、isa的结构和走位,知道了对象通过isa关联到类。本文就开始学习类的原理。

1. 类的本质

1.1 类的本质

想要分析OC中类的结构,我们可以通过clang命令得到底层的实现:

clang -rewrite-objc main.m -o main.cpp

阅读cpp文件发现:Class真正类型是objc_class。

typedef struct objc_class *Class;

下面我们就看下objc_class类型的结构体的实现:

struct objc_class : objc_object {
    // Class ISA;           // 8
    Class superclass;       // 8
    cache_t cache;          // 16 不是8     // formerly cache pointer and vtable
    class_data_bits_t bits;                 // class_rw_t * plus custom rr/alloc flags

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

objc_class继承于objc_object

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

从objc_class结构体,我们可以看出objc_class主要由superclass、cache、bits组成的。objc_class继承自objc_object

> 结论:类的本质是objc_class类型的结构体,objc_class继承于objc_object,所以满足万物皆对象

1.2 objc_object 和 NSObject的关系

继承于objc_object就满足万物皆对象呢?

我们来查看下NSObject源码

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

> NSObject是OC版本的objc_object,和objc_object的定义是一样的,在底层会编译成objc_object

2. 类的结构

我们已经学习了类的本质、类的继承关系,那么我们定义的属性和方法存放在类结构哪里呢?

通过objc_class源码可以得出,类有4个属性:isa、superclass、cache、bits。

2.1 Class ISA

实例对象通过isa关联类,类对象通过isa关联元类。Class是个指针,占用8字节。

2.2 Class superClass

类的父类,一般为NSObject。Class类型的,占用8字节

2.3 cache_t cache

cache主要是用来存储方法缓存链表_buckets,和mask(分配用来缓存bucket的总数),还有当前实际占用的bucket个数。 16字节。

struct cache_t {
    struct bucket_t *_buckets; // 8 结构体指针
    mask_t _mask;              // 4
    mask_t _occupied;          // 4
    ...
};

2.4 class_data_bits_t bits

  1. 先看bits的定义
struct class_data_bits_t {

    uintptr_t bits;
private:
    bool getBit(uintptr_t bit)
    {
        return bits & bit;
    }
    ...
public:

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

源码看着是懵懵的,但是通过data字段,隐约推断出是存放数据的地方。

  1. 另外,在objc_class结构体中,有一句注释:class_data_bits_t bits 相当于 class_rw_t * plus custom rr/alloc flags
// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
// class_rw_t * plus custom rr/alloc flags

我们可以理解为,bits中主要有两部分:

  • 为我们提供了便捷方法用于返回其中的 class_rw_t * 指针:
  • 一个long类型的bits标志位,这个标志位存储了很多flags(比如:快速分配内存标志、是否有析构函数、是否有构造函数、是否有自定义控件等)
  1. 再看一下class_rw_t的结构
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
  ...
}

看到了什么? methods、properties、protocols。这里我们可以大胆猜测,bits中可能存放有我们定义的属性以及方法。但是一切都是猜测,下面我们就验证一下。

2. 类的属性和方法

  1. 为AKPerson添加成员属性、属性变量、类方法、实例方法。
@interface AKPerson : NSObject {
    NSString *hobby;
}

@property (nonatomic, copy) NSString *name;

- (void)personInstanceMethed;
+ (void)personClassMethod;

@end

@implementation AKPerson

- (void)personInstanceMethed {}
+ (void)personClassMethod {}

@end
  1. x/4gx 打印clz的内容
(lldb) x/4gx clz
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da280 0x0000000000000000
(lldb) 
  1. 其中isa8字节,superClass8字节,cache16字节,首地址0x1000023b0偏移32个字节,得出bits的内容。
(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $2 = 0x00000001000023d0
  1. 根据class_rw_t *data() { return bits.data(); }打印bits.data()
(lldb) p $2->data()
(class_rw_t *) $4 = 0x0000000100fd3150
(lldb) p *$4
(class_rw_t) $5 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002308
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002240
        arrayAndFlag = 4294976064
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x00000001000022f0
        arrayAndFlag = 4294976240
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}

2.1 查找属性

打印ro.properties得到property_array_t。property_array_t为二维数组,探索其内部的list,打印property_array_t.list.first。

(lldb) p $5.properties
(property_array_t) $6 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000022f0
      arrayAndFlag = 4294976240
    }
  }
}
(lldb) p $6.list
(property_list_t *) $7 = 0x00000001000022f0
(lldb) 
(lldb) p $7.first
(property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_Name")
  Fix-it applied, fixed expression was: 
    $7->first
(lldb) 
// entsize_list_tt and property_list_t
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
}

至此,我们在rw.properties找到了属性name。

2.2 查找成员变量

属性我们顺利找到了,但是成员变量呢?

来看看rw的ro源码

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;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

发现了什么?const ivar_list_t * ivars,再次隐约感觉就是它了,继续测试。

(lldb) p $6.ro
(const class_ro_t *) $4 = 0x0000000100002308
(lldb) p *$7
(const class_ro_t) $8 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f89 "\x02"
  name = 0x0000000100001f80 "AKPerson"
  baseMethodList = 0x0000000100002240
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000022a8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000022f0
}
(lldb) p $8.ivars
(const ivar_list_t *const) $7 = 0x00000001000022a8
(lldb) p *$9
(const ivar_list_t) $10 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x0000000100002378
      name = 0x0000000100001e64 "hobby"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

至此,我们在现ro.rw.ivar_list_t找到了成员变量。

2.3 查找实例方法

  1. 同理,我们p rw.methods.list找到实例方法
(lldb) p $5.methods
(method_array_t) $12 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002240
      arrayAndFlag = 4294976064
    }
  }
}
(lldb) p $12.list
(method_list_t *) $13 = 0x0000000100002240
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "personInstanceMethed"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (2`-[AKPerson personInstanceMethed] at AKPerson.m:25)
    }
  }
}
  1. 但是发现method_t的count = 4。也就是还有另外3个方法,继续打印发现其他三个方法分别为C++的析构函数destruct方法,属性name的getter和setter方法
(lldb) p $14.get(1)
(method_t) $15 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001c60 (2`-[AkPerson .cxx_destruct] at AkPerson.m:23)
}
(lldb) p $14.get(2)
(method_t) $16 = {
  name = "name"
  types = 0x0000000100001f93 "@16@0:8"
  imp = 0x0000000100001bf0 (2`-[AkPerson name] at AkPerson.h:29)
}
(lldb) p $14.get(3)
(method_t) $17 = {
  name = "setName:"
  types = 0x0000000100001f9b "v24@0:8@16"
  imp = 0x0000000100001c20 (2`-[AkPerson setName:] at AkPerson.h:29)
}

至此,我们在现rw.method_list_t找到了实例方法。

2.4 查找类方法

浏览rw/ro的源码,没有发现类方法的存储位置。而objc_class的其他成员superClass/cache明显也不是。只剩下isa。根据isa的走位,父子类的isa最后都指向根元类。那么类方法是不是在根元类呢?

通过isa & mask找到根元类,然后p rw.method_list_t.list.

(lldb) p $26.list
(method_list_t *) $27 = 0x00000001000021d8
(lldb) p *$27
(method_list_t) $28 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001bc0 (2`+[AKPerson classMethod] at AKPerson.m:27)
    }
  }
}

2.5 总结

  • 属性存在 类 的 bit.data.rw.property
  • 成员变量存在 类 的bit.data.rw.ro.ivar
  • 实例方法存在 类 的 bit.data.rw.method
  • 类方法以实例方法的形式,存在元类。元类又继承自NSObject,形成一个闭环

2.6 clang验证

3. 参考资料

  • NSObject结构图