「OC」源码学习------objc_class的bits成员探究
类模版
objc
@interface GGObject : NSObject
{
int _sum;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, assign) short number;
- (void)speak;
- (void)sayHello;
+ (void)walk;
@end
@implementation GGObject
- (void)speak {
NSLog(@"%s", __func__);
}
+ (void)walk {
NSLog(@"%s", __func__);
}
-(void) sayHello {
NSLog(@"%s", __func__);
}
@end
objc_class的成员变量
在全局搜索搜索objc_class 的内容,我们看到内容如下

类本身自带的isa
指针为八字节;其中superclass
为class类型本质为isa
指针,大小也为八字节;cache_t
结构体的内容得在lldb之中进行分析,查看cache_t的结构体数据,使用p sizeof(cache_t)
可以看到,cache_t的内存为16字节,结合我们前面所计算的,那么我们不难得出。只要我们在objc_class的首地址加上32字节就可以得到bits之中的信息

如何获取bits
既然已经知道平移32位就能获取bits的相关信息,那在lldb里面就不难操作了,我们进入class_data_bits_t
的声明,看到data()
方法
objc
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
大致意思是,将bit的内容转化为class_rw_t
的类型输出

Class_rw_t
class_rw_t
是由class_data_bits_t
中的bits
第3位到46位存储的。
接着在rw
之中获取到对应的方法列表

在源码之中查看class_rw_t
的定义
objc
struct class_rw_t {
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;
void setFlags(uint32_t set)
{
OSAtomicOr32Barrier(set, &flags);
}
void clearFlags(uint32_t clear)
{
OSAtomicXor32Barrier(clear, &flags);
}
void changeFlags(uint32_t set, uint32_t clear)
{
assert((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}
};
使用函数打印属性列表
c
// 获取类的属性
void wj_class_copyPropertyList(Class pClass) {
unsigned int outCount = 0;
objc_property_t *perperties = class_copyPropertyList(pClass, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = perperties[i];
const char *cName = property_getName(property);
const char *cType = property_getAttributes(property);
NSLog(@"name = %s type = %s",cName,cType);
}
free(perperties);
}
可以看到class_rw_t
的结构是由method_array_t
,property_array_t
,protocol_array_t

得到的结果如上:
T
表示type
@
表示变量类型
C
表示copy
N
表示nonatomic
V
表示variable
变量,即下划线变量、
使用LLDB调试出属性列表
lldb
p/x cls
(Class) 0x0000000100008410 GGObject
(lldb) ex (class_data_bits_t *)0x0000000100008430
(class_data_bits_t *) $0 = 0x0000000100008430
(lldb) ex $0->data()
(class_rw_t *) $2 = 0x00006000036f9800
(lldb) ex *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000424
}
}
firstSubclass = nil
nextSiblingClass = NSProcessInfo
}
(lldb) ex $3.properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
storage = (_value = 4295000336)
}
}
(lldb) p/x 4295000336
(long) 0x0000000100008110
(lldb) ex (property_list_t *)0x0000000100008110
(property_list_t *) $5 = 0x0000000100008110
(lldb) p $5->count
(uint32_t) 5
(lldb) p $5->get(0)
(property_t) (name = "name", attributes = "T@\"NSString\",C,N,V_name")
使用LLDB调试出方法列表
lldb
(lldb) ex $3.methods()
(const method_array_t) $6 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
storage = (_value = 4294982464)
}
}
(lldb) p/x 4294982464
(long) 0x0000000100003b40
(lldb) ex (method_list_t *)0x0000000100003b40
(method_list_t *) $7 = 0x0000000100003b40
(lldb) ex *$7
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 2147483660, count = 13)
}
(lldb) ex $8->get(0).small()
(method_t::small) $9 = {
name = (offset = 18432)
types = (offset = 940)
imp = (offset = -2284)
}
(lldb) ex $9.name
(RelativePointer<const void *, false>) $10 = (offset = 18432)
我们可以看到count=13,原因就是13 = (五个属性的getter/setter) + sayHello + speak + 析构函数.cxx_destruct
注意:不是每个类都有析构函数.cxx_destruct
的!析构函数的作用是用来释放C++ 成员对象 和 @property
属性的,若类的实例变量为基本数据类型(如 int
、float
),则不会生成 .cxx_destruct
,因为无需 ARC 管理内存
剩余的协议列表其实就是使用以上方法调试出来,这里不过多赘述
class_ro_t
打印class_ro_t
之中的成员变量列表

查看其成员列表:
objc
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
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 存储的大多是类在编译时就已经确定的信息。(区别于class_rw_t
, 提供了运行时对类拓展的能力)。
三者的关系
ro
:是在类第一次从磁盘被加载到内存中产生,它是一块纯净的内存clear memory
(只读的)
,编译期生成的只读结构体,存储类的静态元数据,如方法列表、属性列表、协议列表、成员变量等。
当进程内存不够时候,ro
可以被移除,在需要的时候再去磁盘中加载,从而节省更多内存。
rw
:程序运行就必须一直存在,在进程运行时类一经使用后,runtime就会把ro
写入新的数据结构dirty memory
(读写的)
,这个数据结构存储了只有在运行时才会生成的新信息。(例如创建一个新的方法缓存并从类中指向它)
于是所有类都会链接成一个树状结构,这是通过First Subclass
(子类)和Next Sibling Class
(兄弟类)指针实现的,这就决定了runtime能够遍历当前使用的所有类。
为了验证runtime
会把ro
的数据写入rw
,我们可以使用lldb进行调试,得出ro
与rw
的方法列表指向的地址都指向了相同的位置
(lldb)ex *$1
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000424
}
}
firstSubclass = nil
nextSiblingClass = NSProcessInfo
}
(lldb) ex *$2
(const class_ro_t) $4 = {
flags = 388
instanceStart = 8
instanceSize = 48
reserved = 0
= {
ivarLayout = 0x0000000100003ed7 "\""
nonMetaclass = 0x0000000100003ed7
}
name = {
std::__1::atomic<const char *> = "GGObject" {
Value = 0x0000000100003ece "GGObject"
}
}
baseMethods = (_value = 4294982464)
baseProtocols = (_value = 0)
ivars = 0x0000000100008048
weakIvarLayout = 0x0000000000000000
baseProperties = (_value = 4295000336)//ro之中方法列表的地址
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p/x 4294982464
(long) 0x0000000100003b40
(lldb)ex $3->methods()
(const method_array_t) $8 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
storage = (_value = 4294982464)//rw之中方法列表的地址
}
}
为社么需要把ro
之中的内容复制到rw
之中呢?其实在寒假之前学过,我们可以通过分类的方法去重写原本类中的对象方法,那么由于ro
是只读的属性,所以需要rw
来负责追踪。
另外从ro将方法列表复制到rw_ext
的源码如下
objc
// 此处仅声明 extAlloc 函数
//(此函数的功能是进行 class_rw_ext_t 的初始化)
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
// extAlloc 定义位于 objc-runtime-new.mm 中,主要完成 class_rw_ext_t 变量的创建,
// 以及把其保存在 class_rw_t 的 ro_or_rw_ext 中。
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
// 加锁
runtimeLock.assertLocked();
// 申请空间
auto rwe = objc::zalloc<class_rw_ext_t>();
// class is a metaclass
// #define RO_META (1<<0)
// 标识是否是元类,如果是元类,则 version 是 7 否则是 0
rwe->version = (ro->flags & RO_META) ? 7 : 0;
// 把 ro 中的方法列表追加到 rw(class_rw_ext_t) 中
//(attachLists 函数等下在分析 list_array_tt 时再进行详细分析)
method_list_t *list = ro->baseMethods();
if (list) {
// 是否需要对 ro 的方法列表进行深拷贝,默认是 false
if (deepCopy) list = list->duplicate();
// 把 ro 的方法列表追加到 rwe 的方法列表中
//(attachLists 函数在分析 list_array_tt 时再进行详细分析)
//(注意 rwe->methods 的有两种形态,可能是指向单个列表的指针,
// 或者是指向列表的指针数组(数组中放的是列表的指针))
rwe->methods.attachLists(&list, 1);
}
// See comments in objc_duplicateClass property lists and
// protocol lists historically have not been deep-copied.
// 请参阅 objc_duplicateClass 属性列表和协议列表中的注释,历史上尚未进行过深度复制。
// This is probably wrong and ought to be fixed some day.
// 这可能是错误的,可能会在某天修改。
// 把 ro 中的属性列表追加到 rw(class_rw_ext_t)中
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rwe->properties.attachLists(&proplist, 1);
}
// 把 ro 中的协议列表追加到 rw(class_rw_ext_t) 中
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// 把 ro 赋值给 rw 的 const class_ro_t *ro,
// 并以原子方式把 rw 存储到 class_rw_t 的 explicit_atomic<uintptr_t> ro_or_rw_ext 中
set_ro_or_rwe(rwe, ro);
// 返回 class_rw_ext_t *
return rwe;
}
由于每一个的类对象都有其对应的rw
,但是上述这样做的结果会导致占用相当相当多的内存,因为据苹果官方统计只有大约10%的类需要真正地去修改它们的方法。
那么如何缩小这些结构呢?于是就设计出了rwe
,从而减少rw
的大小。

rwe
:是在category
被加载或者通过 Runtime API
对类的方法属性协议等等进行修改后产生的,它保存了原本rw
中类可能会被修改的东西(Methods
/ Properties
/ Protocols
/ Demangled Name
),它的作用是给rw
瘦身
在方法实现中来做区分,如果有rw_ext的类,其列表就错那个rw_ext中获得,如果没有,从ro中读取。
