当前位置: 首页 > backend >正文

「OC」源码学习——objc_class的bits成员探究

「OC」源码学习——objc_class的bits成员探究

类模版

@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 的内容,我们看到内容如下

image-20250427192832522

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

image-20250427194349585

如何获取bits

既然已经知道平移32位就能获取bits的相关信息,那在lldb里面就不难操作了,我们进入class_data_bits_t的声明,看到data()方法

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

大致意思是,将bit的内容转化为class_rw_t的类型输出

image-20250427205702571

Class_rw_t

class_rw_t是由class_data_bits_t中的bits第3位到46位存储的。

接着在rw之中获取到对应的方法列表

img

在源码之中查看class_rw_t的定义

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

使用函数打印属性列表

// 获取类的属性
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_tproperty_array_t,protocol_array_t

image-20250429114405295

得到的结果如上:

  • T 表示 type
  • @ 表示 变量类型
  • C 表示 copy
  • N 表示 nonatomic
  • V 表示 variable 变量,即下划线变量、

使用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 = 2148007936witness = 1ro_or_rw_ext = {std::__1::atomic<unsigned long> = {Value = 4295000424}}firstSubclass = nilnextSiblingClass = 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) 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 属性的,若类的实例变量为基本数据类型(如 intfloat),则不会生成 .cxx_destruct,因为无需 ARC 管理内存

剩余的协议列表其实就是使用以上方法调试出来,这里不过多赘述

class_ro_t

打印class_ro_t之中的成员变量列表

image-20250428201825258

查看其成员列表:

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进行调试,得出rorw的方法列表指向的地址都指向了相同的位置

(lldb)ex *$1
(class_rw_t) $3 = {flags = 2148007936witness = 1ro_or_rw_ext = {std::__1::atomic<unsigned long> = {Value = 4295000424}}firstSubclass = nilnextSiblingClass = NSProcessInfo
}
(lldb) ex *$2
(const class_ro_t) $4 = {flags = 388instanceStart = 8instanceSize = 48reserved = 0= {ivarLayout = 0x0000000100003ed7 "\""nonMetaclass = 0x0000000100003ed7}name = {std::__1::atomic<const char *> = "GGObject" {Value = 0x0000000100003ece "GGObject"}}baseMethods = (_value = 4294982464)baseProtocols = (_value = 0)ivars = 0x0000000100008048weakIvarLayout = 0x0000000000000000baseProperties = (_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的源码如下

    // 此处仅声明 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 否则是 0rwe->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 的方法列表进行深拷贝,默认是 falseif (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的大小。

img

rwe:是在category被加载或者通过 Runtime API 对类的方法属性协议等等进行修改后产生的,它保存了原本rw中类可能会被修改的东西(Methods / Properties / Protocols / Demangled Name),它的作用是给rw瘦身

在方法实现中来做区分,如果有rw_ext的类,其列表就错那个rw_ext中获得,如果没有,从ro中读取。

image-20250502144704360

参考文章

Objective-C 类的底层探索

iOS八股文(四)类对象的结构(下)

iOS-底层原理 08:类 & 类结构分析

iOS 从源码解析Runtime (十二):聚焦objc_class(class_rw_t 内容篇)

http://www.xdnf.cn/news/4109.html

相关文章:

  • 五一作业-day02
  • 软件设计师-错题笔记-程序语言
  • 《Effective java》 第三版 核心笔记
  • 蓝桥杯嵌入式按键长短按移植
  • LeetCode:链表的中间结点
  • Linux的时间同步
  • HTTP/HTTPS协议(请求响应模型、状态码)
  • 1247: 彩色的棋子(chess)
  • 《Spring 中 @Autowired 注解详解》
  • 2023年408真题及答案
  • 读《人生道路的选择》有感
  • Day11 训练
  • 30天开发操作系统 第27天 -- LDT与库
  • Gateway网关:路由和鉴权
  • LeetCode 238:除自身以外数组的乘积(Java实现)
  • CRM客户关系管理系统高级版客户管理销售管理合同管理数据分析TP6.0框架源码
  • 阿里云服务器深度科普:技术架构与未来图景
  • kdump详解
  • 基于SRS实现流媒体服务器(最简单的流媒体服务器)
  • 【外围电路】0.介绍
  • react路由使用方法
  • 【ArUco boards】标定板检测
  • 《架构安全原则》解锁架构安全新密码
  • c++进阶——AVL树主要功能的模拟实现(附带旋转操作讲解)
  • ADK 第四篇 Runner 执行器
  • 把Android设备变成“国标摄像头”:GB28181移动终端实战接入指南
  • 博图V20编译报错:备不受支持,无法编译。请更改为受支持的设备。
  • C++初学者的入门指南
  • [Windows] 批量修改文件/文件夹时间戳工具 NewFileTime 7.71
  • VUE3报错 ReferenceError: Cannot access ‘openInit‘ before initialization