iOS 类对象 元类对象(objc_class)的底层探究 - 476139183/Learning-iOS GitHub Wiki
相关博客:https://www.infoq.cn/article/kY9pK5jP1ZUxfOagRlqO/
我们搜索,可以得到结构体如下:
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指令:
-
我们先对其 强转成
class_data_bits_t类型:p (class_data_bits_t *) 0x1000012f0 (class_data_bits_t *) $0 = 0x00000001000012f0
-
然后直接读取 data 数据,获取
class_rw_t结构体:p $0->data() (class_rw_t *) $1 = 0x0000000100a71a60
-
读取 rw 内部的
class_ro_t结构体:p $1->ro (const class_ro_t *) $2 = 0x0000000100001170
-
读取 ro 结构体里面的 baseMethodList:
p $2->baseMethodList (method_list_t *const) $3 = 0x00000001000010e8
-
读取
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;
}
解析结果如下:


