1. OC对象isa指针指向探究
前几篇文章我们对OC
类的创建以及isa
指针的数据存储结构进行了探究,接下来我们就要对isa
指针指向进行探究。
1.1 使用LLDB命令查看isa指针指向
首先,定义一个继承自NSObjec
基类的Person
类,在main
函数中创建一个Person
对象p
,代码如下:
@interface Person : NSObject
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
}
return 0;
}
复制代码
在创建完Person
类对象那行代码打上断点,运行程序,首先打印p
对象的内存数据情况,如下图所示:
获取到p
对象的isa
指针的值之后,就可以通过LLDB
命令打印Person
类对象内存数据情况了,如下图所示:
获取到Person
类对象isa
指针的值之后,其实这个isa
指针的值指向的就是元类对象的地址,通过类对象中的isa
指针的值打印元类对象的内存情况,如下图所示:
获取到元类对象的内存数据之后,我们就可以通过这个元类对象的isa
指针的值获取到根元类对象的内存地址,我们最后来查看一下这个根元类对象的内存数据情况,如下图所示:
最后通过打印的数据我们发现根元类对象的isa
指针指向的起始就是其本身的内存地址。那么在这个过程中,我们就有个疑惑了,因为我们发现打印的类对象
与打印的元类对象都是Person
,但是它们的isa
指针的值却不一样(一个是0x1000080e0
,一个是0x1000080b8
),那怎么验证0x1000080b8
这个就是元类对象的地址呢?我们可以查看这个MachO文件的二进制信息,查看类列表与类引用列表信息,如下图所示:
可以发现这两个表中就只有Person
类的信息,除此之外,就再也找不到其他类的信息了,但是我们可以在符号表中搜索到如下图所示信息:
其中_OBJC_METACLASS_$_Person
就是在编译的过程中系统以及编译器自动生成的元类。
1.2 使用NSLog打印输出对象isa指针链信息
然后紧接着,我们来看看在Person
对象的创建过程中会生成几个类对象,在程序中运行如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class pClass1 = [[Person alloc] class];
Class pClass2 = [Person class];
Class pClass3 = object_getClass([Person alloc]);
Class pClass4 = [[Person alloc] class];
NSLog(@"\n cls1: %p\n cls2: %p\n cls3:%p\n cls4:%p\n", pClass1, pClass2, pClass3, pClass4);
}
return 0;
}
复制代码
运行代码,输出结果如下所示:
根据输出结果我们发现无论我们创建多少个Person
对象,但获取的类对象都是同一个,因此我们也可以猜测一下,创建的Person
对象中其isa
指针链中最终所指向的根元类对象可能与创建的NSObject
对象的isa
指针链中的类对象、元类对象或根元类对象有一定的关系,为了验证这一猜测,我们在程序中编写如下代码来调试运行一下,代码如下:
void personClassInfo(void) {
Person *p = [Person alloc];
Class pCls = [p class];
Class pMetaCls = object_getClass(pCls);
Class pRootMetaCls = object_getClass(pMetaCls);
Class pRootRootMetaCls = object_getClass(pRootMetaCls);
NSLog(@"\n pCls: %p\n pMetaCls: %p\n pRootMetaCls: %p\n pRootRootMetaCls: %p\n", pCls, pMetaCls, pRootMetaCls, pRootRootMetaCls);
}
void objectClassInfo(void) {
//NSObject实例对象
NSObject *obj = [NSObject alloc];
//获取NSObject类
Class cls = object_getClass(obj);
//获取NSObject元类
Class metaCls = object_getClass(cls);
//获取NSObject根元类
Class rootMetaCls = object_getClass(metaCls);
//获取NSObject根根元类
Class rootRootMetaCls = object_getClass(rootMetaCls);
NSLog(@"\n cls: %p\n metaCls: %p\n rootMetaCls: %p\n rootRootMetaCls: %p\n", cls, metaCls, rootMetaCls, rootRootMetaCls);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
personClassInfo();
objectClassInfo();
}
return 0;
}
复制代码
运行程序,查看打印输出的信息,如下图所示:
我们再定义一个Student
类,继承自Person
类,查看一下Student
类isa
指针的指向链,代码如下:
@interface Student : Person
@end
@implementation Student
@end
void studentClassInfo(void) {
Student *s = [Student alloc];
Class sCls = [s class];
Class sMetaCls = object_getClass(sCls);
Class sRootMetaCls = object_getClass(sMetaCls);
Class sRootRootMetaCls = object_getClass(sRootMetaCls);
NSLog(@"\n sCls: %p\n sMetaCls: %p\n sRootMetaCls: %p\n sRootRootMetaCls: %p\n", sCls, sMetaCls, sRootMetaCls, sRootRootMetaCls);
}
//main函数代码如下
int main(int argc, const char * argv[]) {
@autoreleasepool {
studentClassInfo();
personClassInfo();
objectClassInfo();
}
return 0;
}
复制代码
运行程序,查看打印输出结果,如下图所示:
根据以上探究的结论我们可以绘制出以下的对象isa
指针指向链图,如下所示:
2. OC对象类以及元类继承链探究
探究过OC
对象isa
指针的指向链后,我们再来探究一下OC
对象类的继承链,编写代码如下:
void objClassInheritInfo(void) {
NSObject *obj = [[NSObject alloc] init];
Person *p = [[Person alloc] init];
Student *s = [[Student alloc] init];
Class objCls = object_getClass(obj);
Class pCls = object_getClass(p);
Class sCls = object_getClass(s);
Class objClsSuperCls = class_getSuperclass(objCls);
Class pClsSuperCls = class_getSuperclass(pCls);
Class sClsSuperCls = class_getSuperclass(sCls);
NSLog(@"\n sCls: %p , sClsSuperCls: %p\n pCls: %p , pClsSuperCls: %p\n objCls: %p , objClsSuperCls: %p\n", sCls, sClsSuperCls, pCls, pClsSuperCls, objCls, objClsSuperCls);
Class objMetaCls = object_getClass(objCls);
Class pMetaCls = object_getClass(pCls);
Class sMetaCls = object_getClass(sCls);
Class objMetaClsSuperCls = class_getSuperclass(objMetaCls);
Class pMetaClsSuperCls = class_getSuperclass(pMetaCls);
Class sMetaClsSuperCls = class_getSuperclass(sMetaCls);
NSLog(@"\nsMetaCls: %p , sMetaClsSuperCls: %p\n pMetaCls: %p , pMetaClsSuperCls: %p\n objMetaCls: %p , objMetaClsSuperCls: %p\n", sMetaCls, sMetaClsSuperCls, pMetaCls, pMetaClsSuperCls, objMetaCls, objMetaClsSuperCls);
}
复制代码
编译运行程序,查看运行结果,如下图所示:
根据以上的运行结果,我们可以知道OC
对象的类以及元类也存在着继承链,继承关系如下图所示:
最后,我们将isa
指针链与类与元类的继承链做一个汇总,得到的最终关系图如下图所示:
3. 内存对齐
接下来在程序中编写如下代码:
void memoryOffset(void) {
int a = 10; //
int b = 10; //
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);
// 对象 -
Person *p1 = [Person alloc];
Person *p2 = [Person alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
// 数组指针
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);
for (int i = 0; i<4; i++) {
int value = *(d+i);
NSLog(@"%d",value);
}
// OC 类 结构 首地址 - 平移一些大小 -> 内容
// LGPerson.class地址 - 平移 所有的值
NSLog(@"指针 - 内存偏移");
}
复制代码
在main
函数中调用这个函数,并在各个NSLog
函数打上断点,运行程序,查看各个局部变量的值及其在内存中的地址,a
,b
变量输出情况如下所示:
a
,b
变量在内存空间栈区域分配的地址偏移相差4
字节,正好是其类型值int
所占内存的大小。再来看看Person
类对象在内存中分配的内存地址,如下图所示:
在上图每行的打印中第一个值是Person
类对象在内存堆区域中分配的内存首地址,第二个值是p1
指针变量在内存栈区域分配的内存首地址,与前面a
,b
两个int
类型的变量相比,示意图如下所示:
根据上图我们可以知道变量在内存中的分配地址以及大小情况,那么数组变量的分配情况又是如何呢?我们来看看数组元素地址分配情况,如下图所示:
我们发现只要我们知道数组的元素的首地址,想要获取特定位置元素的偏移值,只需要将数组元素首地址加上该元素在数组中的偏移值就可以获取得到该元素所在的内存地址,获取这个元素的值了,其实在结构体中,我们如果想要获取其某个成员变量的值,也只需要将这个结构体变量在内存中分配的首地址加上这个成员变量在结构体中的偏移量就可以获取到这个成员变量的值了,这也是在程序运行过程中获取类结构体中某个成员变量的值的关键所在。
4. 类的结构分析
4.1 类结构源码探究分析
前几篇文章,我们已经探究过isa
指针的类型在底层其实就是objc_class *
,那么我们如果要深入探究类的结构就要从结构体objc_class
入手,首先我们在objc源码中搜索objc_class结构体的代码,可以找到下面两个定义,第一个定义代码如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
复制代码
但是这个定义在现在的iOS
版本中已经被废弃不再使用了,我们直接探究第二个objc_class
结构体定义的部分就可以了,其构造方法以及成员变量代码如下所示:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA; //占8字节
Class superclass; //占8字节
cache_t cache; //占16字节,cache_t这个结构体中有一个8字节大小的成员变量_bucketsAndMaybeMask以及一个8字节大小的共用体类型的成员变量 // formerly cache pointer and vtable
class_data_bits_t bits;
}
复制代码
通过上面的代码我们发现,其实结构体objc_class
继承了结构体objc_object
,而在结构体objc_object
中就有一个8字节大小的(objc_class *
类型的)isa
指针,第二个成员变量也是一个8字节大小的(objc_class *
类型的superclass
指针,第三个成员变量是一个结构体变量(cache_t
类型)cache
,第四个成员变量是一个结构体变量(class_data_bits_t
类型)bits
,其实从bits
这个结构体中可以获取类数据的二进制信息,我们再来查看class_data_bits_t
结构体中的成员变量,以及部分重要方法,代码如下所示:
struct class_data_bits_t {
uintptr_t bits;
...
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
}
复制代码
通过其成员属性bits(64位系统中是unsigned long类型),可以获取到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;
public:
//获取类中定义的方法列表
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
//获取类中定义的属性列表
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
//获取类中遵守的协议列表
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
复制代码
在上面的代码中methods()函数返回的类型method_array_t其实是一个C++类,其代码定义如下:
class method_array_t :
public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;
public:
method_array_t() : Super() { }
method_array_t(method_list_t *l) : Super(l) { }
const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
return beginLists();
}
const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};
复制代码
其中method_array_t
又是继承自类list_array_tt
,而在类list_array_tt
中又定义了一个结构体array_t
,以及两个成员变量,代码如下:
class list_array_tt {
struct array_t {
uint32_t count;
Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
size_t byteSize() {
return byteSize(count);
}
};
public:
union {
Ptr<List> list;
uintptr_t arrayAndFlag;
};
}
复制代码
除此以外还定义了一个iterator
类,用来获取array_t
中的数据,
4.2 类的方法列表结构探究
创建一个LGPerson
类,然后在main函数中创建一个LGPerson
类对象,通过LLDB
调试看是否能获取到LGPerson
类中定义的方法列表以及属性列表,代码如下所示:
//LGPerson.h文件
@interface LGPerson : NSObject {
NSString *key;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) BOOL isFun;
@property (nonatomic, assign) double height;
- (void)sayHello;
+ (void)sayWorld;
@end
//LGPerson.m文件
@implementation LGPerson
- (void)sayHello {
NSLog(@"hello");
}
+ (void)sayWorld{
NSLog(@"world");
}
@end
//main函数中代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
p.name = @"sss";
p.hobby = @"play";
p.isFun = true;
p.height = 183.5;
}
return 0;
}
复制代码
在创建p
对象的下一行打上断点,运行程序,在LLDB
中进行调试,步骤如下:
- 获取
LGPerson
对象p
中isa
指针的值
- 获取
LGPerson
类对象的内存地址
- 首地址偏移
32
字节获取指向objc_class
结构体中成员变量bits
的地址指针
- 调用
data()
函数获取到class_rw_t *
类型的指针变量
- 通过获取到的
class_rw_t *
类型的指针变量获取class_rw_t
结构体变量
- class_rw_t结构体变量调用methods()函数获取C++类method_array_t的实例化对象
- 获取方法列表
- 获取方法列表数组指针以及method_list_t类实例对象
- 依次输出这个方法列表数组中的数据
通过以上探究,我们发现除了属性的get、set方法以及自定义的实例方法之外,并不包含定义的类方法。
4.2 类的属性列表结构探究
探究完类的方法列表结构之后,紧接着我们在上面LLDB操作的基础上再来探究类的属性列表结构是如何的
- 获取property_array_t结构体变量
- 获取属性列表数组指针以及property_list_t类实例化对象
- 打印输出属性列表信息
通过以上探究,我们发现除了属性列表中只包含定义的OC
对象的属性,并不包含OC
类中定义的成员变量。
4.3 类方法结构探究
既然在类的属性列表以及方法列表中找不到定义的类方法,我们就猜测类方法可能存在在元类中,探究过程如下所示:
(lldb) p/x p
(LGPerson *) $0 = 0x0000000101a252a0
(lldb) x/4gx $0
0x101a252a0: 0x011d8001000084d1 0x0000000000000000
0x101a252b0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d8001000084d1 & 0x00007ffffffffff8
(long) $1 = 0x00000001000084d0
(lldb) x/4gx 0x00000001000084d0
0x1000084d0: 0x00000001000084a8 0x000000010036c140
0x1000084e0: 0x00000001003643b0 0x0000803800000000
(lldb) p/x 0x00000001000084a8 & 0x00007ffffffffff8
(long) $2 = 0x00000001000084a8
(lldb) p/x (class_data_bits_t *) (0x00000001000084a8 + 0x20)
(class_data_bits_t *) $3 = 0x00000001000084c8
(lldb) p/x *$3
(class_data_bits_t) $4 = (bits = 0x0000000101a25264)
(lldb) p/x $4.data()
(class_rw_t *) $5 = 0x0000000101a25260
(lldb) p/x *$5
(class_rw_t) $6 = {
flags = 0xa0080001
witness = 0x0001
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 0x0000000100008170
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff80171eb0
}
(lldb) p/x $6.methods()
(const method_array_t) $7 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x00000001000081b8
}
arrayAndFlag = 0x00000001000081b8
}
}
}
(lldb) p/x $7.list.ptr
(method_list_t *const) $8 = 0x00000001000081b8
(lldb) p *$8
(method_list_t) $9 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p/x $9.get(0).big()
(method_t::big) $10 = {
name = 0x0000000100003e8e "sayWorld"
types = 0x0000000100003f55 "v16@0:8"
imp = 0x0000000100003c00 (KCObjcBuild`+[LGPerson sayWorld])
}
复制代码
4.3 类中定义的成员变量结构探究
类的成员变量探究过程如下:
(lldb) p/x p
(LGPerson *) $0 = 0x0000000101219de0
(lldb) x/4gx $0
0x101219de0: 0x011d8001000084d1 0x0000000000000000
0x101219df0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d8001000084d1 & 0x00007ffffffffff8
(long) $1 = 0x00000001000084d0
(lldb) p/x (class_data_bits_t *)(0x00000001000084d0 + 0x20)
(class_data_bits_t *) $2 = 0x00000001000084f0
(lldb) p/x *$2
(class_data_bits_t) $3 = (bits = 0x0000000101219dc4)
(lldb) p/x $3.data()
(class_rw_t *) $4 = 0x0000000101219dc0
(lldb) p/x *$4
(class_rw_t) $5 = {
flags = 0x80080000
witness = 0x0000
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 0x00000001000081d8
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff80170a88 NSUUID
}
(lldb) p/x *($5.ro())
(const class_ro_t) $6 = {
flags = 0x00000000
instanceStart = 0x00000008
instanceSize = 0x00000030
reserved = 0x00000000
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "LGPerson" {
Value = 0x0000000100003f38 "LGPerson"
}
}
baseMethodList = 0x0000000100008220
baseProtocols = nil
ivars = 0x0000000100008300
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000083a8
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p/x *($6.ivars)
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 0x00000020, count = 0x00000005)
}
(lldb) p/x $7.get(0)
(ivar_t) $8 = {
offset = 0x0000000100008430
name = 0x0000000100003edc "key"
type = 0x0000000100003f41 "@\"NSString\""
alignment_raw = 0x00000003
size = 0x00000008
}
复制代码
因此最后我们发现类的成员变量存在