前言
在类的实例对象创建过程中,通过alloc的分析,我们知道实例对象通过isa(unoin)存储了对应的class的地址,从而将实例对象和class关联起来。那么,class到底是什么呢?我们来探索下。
预备知识
oc语言是c/c++的超集,为了便于研究底层结构,我们把oc源码转换成c++代码,这就需要使用LLVM的oc语言的前端编译器clang。
1.clang基本用法
clang -rewrite-objc -main.m -o main.cpp
该命令把目标文件编译成c++文件,方便我们研究底层代码。
2.当目标文件中引用了UIKit等framework时,需要指定平台、系统版本、sdk等信息
clang -rewrite-objc -fobjc-arc -fobjc-runtinme=ios-13.2.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk main.m -o main.cpp
如上指令指定了arc、系统版本、模拟器sdk等
3.xcrun clang
xcode安装了xcrun命令,对clang进行了一些封装,更好用写。建议使用。
真机:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm_64.cpp
模拟器:
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m -o main_x86_64.cpp
一、isa的存在性
1.探索源码
@interface LGPerson : NSObject
@property(nonatomic, strong) NSString *name;
@end
@implementation LGPerson
@end
定义一个继承自NSObject的LGPerson类,为了便于研究底层结构,使用clang转成c++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LGPerson.m -o LGPerson_arm64.cpp
针对LGPerson_arm64.cpp进行分析, 搜索LGPerson,找到如下代码块:
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString * _Nonnull _name;
};
主要由两部分组成:
- 定义
LGPerson类
typedef struct objc_object LGPerson;
- 定义结构体
LGPerson_IMPL:
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString * _Nonnull _name;
};
1. 实例对象底层: isa指向objc_class
第二部分代码:
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString * _Nonnull _name;
};
是LGPerson实例对象的底层结构:一个包含数据成员NSObject_IVARS,_name的结构体。
_name就是属性对应的实例变量,用于存储属性数据。- NSObject_IVARS是一个struct,找到其定义:
struct NSObject_IMPL {
Class isa;
};
可知,这是NSObject的实例对象的结构。
typedef struct objc_class *Class;
- isa是一个指向objc_class的结构体指针
- 由此可知:所有的实例对象内部,有一个isa,指向objc_class。
- 通过alloc的源码分析可知:在64位架构下,isa通常情况下对应的不再是一个纯指针,而是一个优化后的nonpointer。
2. objc_class:继承objc_object
struct objc_class : objc_object {
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();
}
...
}
3. objc_object: 只有isa
这样回到了我们已经熟悉的objc_object:
struct objc_object {
private:
isa_t isa;
...
}
4. 总结
- objc_object是所有的基石,是一个结构体,具有isa_t类型的isa
- 类的底层是objc_class结构体,继承自objc_object, 同样具有isa
- 实例对象底层是一个结构体,根据继承规则,最终源头来自于NSObject对应的结构体:只有一个成员:
Class isa,指向objc_class,所以所有的实例对象仍然具有一个isa - 以objc_object为模板,oc中万物都是对象
二.isa的指向性
源码:
LGPerson *person = [[LGPerson alloc] init];
person.name = @"zxdix";
1. 实例对象isa指向类 Class
利用LLDB指令,断点调试,取出实例对象person的isa指向:
2.类的isa指向:元类 metaClass
继续查看LGPerson的内存:
再次查找isa指向,发现仍是一个LGPerson,但是内存地址不同,称之为元类metaClass
3.元类的isa指向:根元类 rootMetaClass, 根元类的isa指向自己
继续探索isa:
发现,元类的isa指向一个NSObject,这个NSObject的isa指向自己。然后机会进入无限循环,不会再出现新的东西了。
那么这个NSObject是什么呢?是我们平时说的根类NSObject(NSObject.class)吗?
由上图可知此处的NSObject并不是根类(NSObject.class),反而是根类(NSObject.class)的isa,称之为根元类
4. isa走向总结:实例对象->类对象->元类对象->根元类对象
根元类也是元类,所以指向自己
用一个经典的图说明如下:(ps:太懒了,偷了一个嘿嘿)
三、superclass的指向
在objc_class定义中, 有一个数据成员superclass:
struct objc_class : objc_object {
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();
}
...
}
顾名思义,应该是指向父类对象,我们通过查看内存验证下(还是研究isa时的使用的代码,懒就一个字):
superclass确实指向父类。
验证一遍类的superclass,同样验证一遍元类metaClass的superclass,可以得到如下的继承图:
有两点需要注意:
- NSObject(class)的superclass是nil
- NSObject(metaClass, 根元类)的superclass是NSObject(class, 根类),也就是说无论是类还是元类,沿着继承树追追溯,最终到nil结束。
结合isa,可以得到一个经典的走位图:(ps:偷图会上瘾的)
四、类的结构初探
objc_class的源码如下:
struct objc_class : objc_object {
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();
}
...
}
分析类对象的内存结构(arm64架构下):粗看,具有4个成员
- 继承自objc_object, 所以第一个变量是isa,8个字节
- superclass, 指针, 8字节
- cache, 陌生的cache_t结构,未知
- bits,未知
1. cache_t分析
源码初看很复杂,有很定未知的定义,但是看结构,分成3个部分:
- static变量不会存在结构体中,忽略
- 第1部分,#if...#elif...#elif..#else...#endif结构,其实就是两个变量:以第一个#if为例:
_buckets、mask。explicit_atomic是个泛型定义,本质存储的是struct bucket_t *指针和mask_t(uint32)。其他if分支也是一样的,长度一致。3.第2部分,2字节:
typedef unsigned short uint16_t(LP64是成立的:指的是long、pointer类型都是64位的); 4.第3部分,同样也是2字节; 所以cache_t的size是: 8 + 4 + 2 + 2 = 16字节
2. 类的内存结构
到此,我们知道objc_class的内存分布如下图:
bits成员的地址偏移是32,则通过class的首地址偏移32字节,即为bits的地址。
3. 获取class_rw_t
利用地址偏移,可以获取bits的地址,从而读取class_rw_t:
4. methods、properties
在class_rw_t中有几个重要的方法:methods、properties。我们可以通过打印输出内容。
- methods:可知里面存放了实例方法的相关信息。
(lldb) p/x LGPerson.class
(Class) $1 = 0x0000000100002188 LGPerson
(lldb) p/x (class_data_bits_t*)(0x00000001000021a8) // 偏移32字节
(class_data_bits_t *) $2 = 0x00000001000021a8
(lldb) p *$2
(class_data_bits_t) $3 = (bits = 4320411204)
(lldb) p $3.data()
(class_rw_t *) $4 = 0x0000000101843e40
(lldb) p *$4
(class_rw_t) $5 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975592
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $5.methods()
(const method_array_t) $6 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020b0
arrayAndFlag = 4294975664
}
}
}
(lldb) p $6.list
(method_list_t *const) $7 = 0x00000001000020b0
(lldb) p *$7
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3
first = {
name = ".cxx_destruct"
types = 0x0000000100000f8d "v16@0:8"
imp = 0x0000000100000e60 (KCObjc`-[LGPerson .cxx_destruct])
}
}
}
(lldb) p $8.get(1)
(method_t) $10 = {
name = "name"
types = 0x0000000100000f95 "@16@0:8"
imp = 0x0000000100000e90 (KCObjc`-[LGPerson name])
}
(lldb) p $8.get(2)
(method_t) $11 = {
name = "setName:"
types = 0x0000000100000f9d "v24@0:8@16"
imp = 0x0000000100000eb0 (KCObjc`-[LGPerson setName:])
}
- properties: 存放了属性相关信息
(lldb) p $5.properties()
(const property_array_t) $12 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002128
arrayAndFlag = 4294975784
}
}
}
(lldb) p $12.list
(property_list_t *const) $14 = 0x0000000100002128
(lldb) p *($14)
(property_list_t) $15 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}