「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 的内容,我们看到内容如下
类本身自带的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()
方法
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
的定义
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_t
,property_array_t
,protocol_array_t
得到的结果如上:
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
属性的,若类的实例变量为基本数据类型(如 int
、float
),则不会生成 .cxx_destruct
,因为无需 ARC 管理内存
剩余的协议列表其实就是使用以上方法调试出来,这里不过多赘述
class_ro_t
打印class_ro_t
之中的成员变量列表
查看其成员列表:
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 = 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
的大小。
rwe
:是在category
被加载或者通过 Runtime API
对类的方法属性协议等等进行修改后产生的,它保存了原本rw
中类可能会被修改的东西(Methods
/ Properties
/ Protocols
/ Demangled Name
),它的作用是给rw
瘦身
在方法实现中来做区分,如果有rw_ext的类,其列表就错那个rw_ext中获得,如果没有,从ro中读取。
参考文章
Objective-C 类的底层探索
iOS八股文(四)类对象的结构(下)
iOS-底层原理 08:类 & 类结构分析
iOS 从源码解析Runtime (十二):聚焦objc_class(class_rw_t 内容篇)