六 OC 底层原理 类的结构
前言
当一个相同的类创建多个对象时,那么这么对象的类是不是创建了多个呢
Class d1 = [Desk class];
Class d2 = [Desk alloc].class;
Class d3 = object_getClass([Desk alloc]);
Class d4 = [Desk alloc].class;
NSLog(@"%p - %p - %p - %p", d1, d2, d3, d4);
--------------
Room[42703:2055484] 0x1000080f0 - 0x1000080f0 - 0x1000080f0 - 0x1000080f0
类对象地址显然相同, 所以 类在内存空间中只存了一份
回顾
上篇我们知道类在底层一个继承于 objc_object 的对象,也就是object_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() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
ASSERT(isFuture() || isRealized());
data()->setFlags(set);
}
}
我们知道 objc_class
第一个内存是 isa
,superclass
存储的父类信息,占据8个字节,cache
缓存占用 16 字节, bits
存储了类的方法,属性,协议等信息的地方,具体的注释表示 class_data_bits_t
相当于 class_rw_t 指针加上 rr/alloc 的标识
class_data_bits_t
结构体返回了 class_rw_t
指针,那 class_rw_t
又是什么呢?
就在代码旁简单谢谢介绍
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
// 当前所属类 第一个子类
Class firstSubclass;
// 当前所有类 的兄弟类
Class nextSiblingClass;
private:
// ro_or_rw_ext_t会有两种情况:
// 1): 值是 class_ro_t *
// 2): 值是 class_rw_ext_t *,
// 而 class_ro_t * 作为 class_rw_ext_t 的 const class_ro_t *ro 成员变量保存
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
... 此处省略
}
// class_rw_ext_t 其中 rw 可以理解为 read write
struct class_rw_ext_t {
// 存放只读类型
const class_ro_t *ro;
// 方法列表
method_array_t methods;
// 属性列表
property_array_t properties;
// 协议列表
protocol_array_t protocols;
// 所属类名
char *demangledName;
// 版本号
uint32_t version;
};
// class_ro_t 其中 ro 可以理解为 read only
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;
... 此处省略
};
可以看出 class_rw_t
存放者类的属性 协议 方法等信息
现在我们验证一下类的成员变量 属性存储的位置
准备
@interface TObject : NSObject
{
NSString *tname;
}
@property (nonatomic, copy) NSString* toName;
+ (void) classMethodTest;
- (void) instanceMethodTest;
@end
@implementation TObject
+(void)classMethodTest{
NSLog(@"%s",__func__);
}
-(void)instanceMethodTest{
NSLog(@"%s",__func__);
}
@end
成员变量 / 属性 存储的位置
接下我 我们就要利用 lldb
调试 一步一步验证
- 我们先要获取类对象的内存地址,通过类结构分析
class_data_bits_t bits
字段之前 还有32 字节
所以我们需要通过内存便宜获取bits
的内存地址
在这我们介绍一个命令x/5gx
其中第一个 x 表示读取内存,5表示分成5段,g 表示一个单元8个字节,x 表示16进制, 所以其中第5个地址 就是 bits 的地址
(lldb) p obj1.class
(Class) $0 = TObject
(lldb) x/5gx $0
0x1000021f0: 0x00000001000021c8 0x0000000100333140
0x100002200: 0x00000001018335c0 0x0001802400000007
0x100002210: 0x00000001006656a4
(lldb)
- 我们现在需要将 地址强转为
class_data_bits_t
类型,并通过 data() 函数 获取class_rw_t
(lldb) p (class_data_bits_t*)0x100002210
(class_data_bits_t *) $1 = 0x0000000100002210
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000020a000000000
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975648
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
- 此时我们调用class_rw_t 结构体中的 properties() 获取属性列表,可以看到属性名称 toName
(lldb) p $3.properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002198
arrayAndFlag = 4294975896
}
}
}
(lldb) p $4.list
(property_list_t *const) $5 = 0x0000000100002198
p *$5
(property_list_t) $6 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "toName", attributes = "T@\"NSString\",C,N,V_toName") // 其中 C 代Copy,N 代表 nonatomic
}
}
- 此时 我们的成员变量又去哪了呢,接下来 我们接着调用 class_rw_t 的 ro属性,并获取 ro 的 ivar
(lldb) p $3.ro()
(const class_ro_t *) $9 = 0x00000001000020a0
(lldb) p $9->ivars
(const ivar_list_t *const) $10 = 0x0000000100002150
(lldb) p *$10
(const ivar_list_t) $12 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000021c0 // 偏移量
name = 0x0000000100000eb9 "tname" // 名称
type = 0x0000000100000f8a "@\"NSString\"" // 类型
alignment_raw = 3 // 8字节对齐
size = 8 // 内存占用大小
}
}
}
由此可见 我们的 属性
是存在 class_rw_t
中的 属性列表
中的,而成员变量
是存储在 class_ro_t
中的 ivars
,成员变量列表中的
实例方法 / 类方法 的存储位置
- 接着上面的调用,我们先获取
class_rw_t
的方法列表methods()
,在读取 列表信息
(lldb) p $3.methods()
(const method_array_t) $7 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020e0
arrayAndFlag = 4294975712
}
}
}
(lldb) p $7.list
(method_list_t *const) $8 = 0x00000001000020e0
(lldb) p *$8
(method_list_t) $9 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3
first = {
name = "instanceMethodTest"
types = 0x0000000100000f34 "v16@0:8"
imp = 0x0000000100000db0 (Objc_alloc`-[TObject instanceMethodTest])
}
}
}
(lldb)
由此可见我们的实例方法
确实是存储在 class_rw_t
中的方法列表中的
- 现在找找我们的类方法存到哪了, 先看看
class_ro_t
中的baseMethodList
lldb) p $3.ro()
(const class_ro_t *) $10 = 0x0000000100002098
(lldb) p $10->baseMethodList
(method_list_t *const) $12 = 0x00000001000020e0
(lldb) p *$12
(method_list_t) $13 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3
first = {
name = "instanceMethodTest"
types = 0x0000000100000f34 "v16@0:8"
imp = 0x0000000100000db0 (Objc_alloc`-[TObject instanceMethodTest])
}
}
}
(lldb)
我们看到 在 class_ro_t
中的方法列表中,还是存储着我们的实例方法信息
那我们的类方法到底去哪了呢,通过 isa
的继承关系 推导,会不会在我们的元类中呢
- 现在我们开始获取元类地址,然后获取元类的
class_rw_t
中的方法列表中,流程和上面的是一样的
(lldb) p objc_getMetaClass(object_getClassName(obj1))
(Class) $14 = 0x00000001000021a8
(lldb) x/5gx $14
0x1000021a8: 0x00000001003330f0 0x00000001003330f0
0x1000021b8: 0x0000000101925020 0x0001e03500000007
0x1000021c8: 0x00000001006ce2e4
(lldb) p (class_data_bits_t*)0x1000021c8
(class_data_bits_t *) $15 = 0x00000001000021c8
(lldb) p $15->data()
(class_rw_t *) $17 = 0x00000001006ce2e0
(lldb) p *$17
(class_rw_t) $18 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975536
}
firstSubclass = nil
nextSiblingClass = 0x00007fff89e08cf8
}
(lldb) p $18.methods
(const method_array_t) $19 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002078
arrayAndFlag = 4294975608
}
}
}
Fix-it applied, fixed expression was:
$18.methods()
(lldb) p $19.list
(method_list_t *const) $20 = 0x0000000100002078
(lldb) p *$20
(method_list_t) $21 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "classMethodTest"
types = 0x0000000100000f34 "v16@0:8"
imp = 0x0000000100000d80 (Objc_alloc`+[TObject classMethodTest])
}
}
}
(lldb)
由此可见 我们的类方法
是存储在对象所属类的元类
当中的方法列表
中
总结
这是一张类的结构图
image.png
- 方法
-
实例方法
确实是存储在class_rw_t
中的methods
中的 -
类方法
存储在 当前对象所属类的元类
中的class_rw_t
中的methods
中
-
- 属性
- 对象属性 ->
class_rw_t
中的properties
中 - 成员变量 ->
class_rw_t
->class_ro_t *ro
->ivars
- 对象属性 ->
补充 - 验证以上
- 例1 通过
class_getInstanceMethod
获取类的实例方法
Class class = TObject.class;
const char *className = class_getName(class);
Class metaClass = objc_getMetaClass(className);
// --- class_getInstanceMethod
Method method1 = class_getInstanceMethod(class, @selector(instanceMethodTest));
Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethodTest));
Method method3 = class_getInstanceMethod(class, @selector(classMethodTest));
Method method4 = class_getInstanceMethod(metaClass, @selector(classMethodTest));
NSLog(@"class_getInstanceMethod - %p-%p-%p-%p",method1,method2,method3,method4);
===========
TObjc[4080:382660] class_getInstanceMethod - 0x100003d51-0x0-0x0-0x100003d39
class_getInstanceMethod
返回的是类的实例方法,如果类或者父类中没有找到,就返回nil
/***********************************************************************
* class_getInstanceMethod. Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
以上 method1 和 method4 是有值的,进一步验证了 实例方法存在类中,类方法存在元类中,
也给了我们另一个定义 也就是说 类方法,对于元类来说 其实就是一个实例方法
- 例2 获取 类方法
class_getClassMethod
// --- class_getClassMethod
Method method5 = class_getClassMethod(class, @selector(instanceMethodTest));
Method method6 = class_getClassMethod(metaClass, @selector(instanceMethodTest));
Method method7 = class_getClassMethod(class, @selector(classMethodTest));
Method method8 = class_getClassMethod(metaClass, @selector(classMethodTest));
NSLog(@"class_getClassMethod - %p-%p-%p-%p",method5,method6,method7,method8);
========
2020-12-15 21:54:56.966095+0800 TObjc[4080:382660] class_getClassMethod - 0x0-0x0-0x100003d39-0x100003d39
class_getClassMethod
这个方法 其实就是使用元类 获取实例方法
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
其实都是走的class_getInstanceMethod
函数,method7 之所以会有值,也是因为 getMeta()
这个函数返回元类的原因, 然而当 传入的是元类的话 就会返回本身,这也就意味着 method7
和method8
其实是一样的
3.例3 获取方法实现class_getMethodImplementation
// 寻找方法实现 class_getMethodImplementation
IMP imp1 = class_getMethodImplementation(class, @selector(instanceMethodTest));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethodTest));
IMP imp3 = class_getMethodImplementation(class, @selector(classMethodTest));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethodTest));
NSLog(@" class_getMethodImplementation %p-%p-%p-%p",imp1,imp2,imp3,imp4);
=======
2020-12-15 21:54:56.966114+0800 KCObjc[4080:382660] class_getMethodImplementation 0x100003ab4-0x199ce6600-0x199ce6600-0x100003a7c
其中有一个很重要的东西 _objc_msgForward
消息转发
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
所以 imp2
和 imp3
是没有找到的,只是进行了消息转发,所以打印都是有值的
class_getInstanceMethod
:返回的是类的实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回空。
class_getClassMethod
:获取类方法,本质上还是调用 class_getInstanceMethod
,不过在这其中的参数 变成了元类,如果传入的类或者父类中 对应的元类中没有找到,那就返回空
class_getMethodImplementation
: 寻找方法实现,如果在对应的类,或者元类中没有找到,那就进行消息转发