iOS 类对象 元类对象(objc_class)的底层探究 - 476139183/Learning-iOS GitHub Wiki

相关博客:https://www.infoq.cn/article/kY9pK5jP1ZUxfOagRlqO/

struct 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() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    
    // 省略下面方法
    
} 

可以看到, 该结构体继承自objc_object, 而 objc_object里面是只有一个 isa 指针,用于指向其元类。objc_class 同时也拥有一个 superclass,用于指向其父类,cache 是其方法缓存,这里暂时先不研究。

所有我们可以判定,类的相关信息应该存放在 bits 里面,而 它又存在一个方法 data(),可以得到 class_rw_t 结构。

  class_rw_t* data() {
     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;
    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;
    // 省略下面方法
}

这里我们发现了我们类的一些常用的信息,但是并未找到成员变量信息,我们再查看一下 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;

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

可以看到的是,我们的 class_ro_t,是只读的,并且, 成员变量也是只读的。

那么通过包含关系,我们串联起来,可以获得 类对象结构, 它们存在如下的关系:

通过查看内部结构,我们发现 cache_t 这个结构体占用了 16个字节

结构体的方法存储推导

方法列表和属性列表、协议列表相似,所以我们这边只分析一下方法列表的存储:

首先我们有如下代码:

#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    
    Person *person = [[Person alloc] init];
    NSLog(@"%p",person.class);
    NSLog(@"%p",object_getClass(person.class));
    NSLog(@"end");
  }
  return 0;
}

进行断点调试,获取类的地址和元类地址。

0x1000012d0
0x1000012a8

我们知道了 class 的结构之后,我们对 isa 地址偏移16个字节,就能得到对应的 rw 结构体,也就是 0x1000012f0, 然后我们开始对其进行lldb指令:

  1. 我们先对其 强转成 class_data_bits_t 类型:

    p (class_data_bits_t *) 0x1000012f0
    (class_data_bits_t *) $0 = 0x00000001000012f0
  2. 然后直接读取 data 数据,获取 class_rw_t 结构体:

    p $0->data()
    (class_rw_t *) $1 = 0x0000000100a71a60
  3. 读取 rw 内部的 class_ro_t 结构体:

    p $1->ro
    (const class_ro_t *) $2 = 0x0000000100001170
  4. 读取 ro 结构体里面的 baseMethodList:

    p $2->baseMethodList
    (method_list_t *const) $3 = 0x00000001000010e8
  5. 读取 method_list_t 数据

    p *$3
    
    (method_list_t) $4 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 1
        first = {
          name = "instanceMethod"
          types = 0x0000000100000f5e "v16@0:8"
          imp = 0x0000000100000cc0 (debug-objc`-[Person instanceMethod] at Person.m:12)
        }
      }
    }
        ```
    

过程截图如下:

我们可以看到, Person 存在一个 实例方法,并且在 Person.m 文件的 第12行代码处。

如果我们有多个方法的话,可以通过 p $3->get(1),获取第二个位置的方法

同理 我们也可以读取 元类 的地址并查看内部数据,地址同样偏移16位:

p (class_data_bits_t *) 0x1000012c8
(class_data_bits_t *) $5 = 0x00000001000012c8

p $5->data()
(class_rw_t *) $6 = 0x0000000100a2df00

p $6->ro
(const class_ro_t *) $7 = 0x0000000100001108

p $7->baseMethodList
(method_list_t *const) $8 = 0x00000001000010e8

p *$8

(method_list_t) $9 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "classMethod"
      types = 0x0000000100000f5e "v16@0:8"
      imp = 0x0000000100000cf0 (debug-objc`+[Person classMethod] at Person.m:16)
    }
  }
}

我们扒完了 方法列表的过程,同样适用于 属性列表、协议列表。这里暂时不演示了。所以我们可以得出, class_ro_t 作为一个只读的数据结构,里面存储着 我们的方法列表,属性列表,那么作为类似的 class_rw_t 结构体,我们发现也有一个 method_list_t 的成员 methods。我们同样读取一下:

p $1->methods
(method_array_t) $10 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100001150
      arrayAndFlag = 4294971728
    }
  }
}

通过窥探 method_array_t 的源码,我们发现有一个 beginCategoryMethodLists()函数,我们进行调用:

p $10.beginCategoryMethodLists()
(method_list_t **) $11 = 0x0000000101e2cdf0

p *$11
(method_list_t *) $12 = 0x0000000100001150

p *$12
(method_list_t) $13 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "instanceMethod"
      types = 0x0000000100000f5e "v16@0:8"
      imp = 0x0000000100000cc0 (debug-objc`-[Person instanceMethod] at Person.m:12)
    }
  }
}

我们发现了,这里同样存在 [Person instanceMethod] ,实际上,ro存储的是类本身编译好的方法,之后会 copy 到 rw 的方法列表里面,而我们动态添加的方法,分类的方法,都是动态的添加到这个 rw 的方法列表里面,并提供给我们调用(属性、协议亦然)。

总结

除了用 lldb 调试以外,我们还可以通过定义类似的结构体,让对象强转为对应的结构体,提取相应的信息。

提前定义好的结构体
#import <Foundation/Foundation.h>

#ifndef XXClassInfo_h
#define XXClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#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;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() { // 提供data()方法进行 & FAST_DATA_MASK 操作
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC对象 */
struct xx_objc_object {
    void *isa;
};

/* 类对象 */
struct xx_objc_class : xx_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    xx_objc_class* metaClass() { // 提供metaClass函数,获取元类对象
// 上一篇我们讲解过,isa指针需要经过一次 & ISA_MASK操作之后才得到真正的地址
        return (xx_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* XXClassInfo_h */

使用详解代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *object = [[NSObject alloc] init];
        Person *person = [[Person alloc] init];
        Student *student = [[Student alloc] init];
        
        xx_objc_class *objectClass = (__bridge xx_objc_class *)[object class];
        xx_objc_class *personClass = (__bridge xx_objc_class *)[person class];
        xx_objc_class *studentClass = (__bridge xx_objc_class *)[student class];
        
        xx_objc_class *objectMetaClass = objectClass->metaClass();
        xx_objc_class *personMetaClass = personClass->metaClass();
        xx_objc_class *studentMetaClass = studentClass->metaClass();
        
        class_rw_t *objectClassData = objectClass->data();
        class_rw_t *personClassData = personClass->data();
        class_rw_t *studentClassData = studentClass->data();
        
        class_rw_t *objectMetaClassData = objectMetaClass->data();
        class_rw_t *personMetaClassData = personMetaClass->data();
        class_rw_t *studentMetaClassData = studentMetaClass->data();

        //
        NSLog(@"%p %p %p %p %p %p",  objectClassData, personClassData, studentClassData,
              objectMetaClassData, personMetaClassData, studentMetaClassData);

    return 0;
}

解析结果如下:

⚠️ **GitHub.com Fallback** ⚠️