iOS 探索类(上)--类的结构
本篇文章我们会继续探索iOS底层非常重要的内容--类。
一、类的关系分析
我们创建两个类:LPPerson
和LPStudent
。LPPerson
继承于NSObject
,LPStudent
继承于LPPerson
:
@interface LPPerson : NSObject
@property (nonatomic) NSString *name;
@end
@interface LPStudent : LPPerson
@end
再在Viewcontroller
中分别创建一个person
和student
对象:
- (void)viewDidLoad {
[super viewDidLoad];
LPPerson *person = [[LPPerson alloc]init];
LPStudent *student = [[LPStudent alloc]init];
NSLog(@"%@",person);
}
类的isa
走向
并在LPStudent *student = [[LPStudent alloc]init];
这一行打上断点,接下来运行代码:
在lldb
调试台中进行如下操作:
(lldb) x/4g person ///获取person内存,并打印4条信息
0x600003cbc180: 0x00000001066cc590 0x0000000000000000
0x600003cbc190: 0x0000000000000000 0x0000000000000000
/// 0x00000001066cc590是person对象的isa指针,0x00000001066cc590 & 0x00007ffffffffff8ULL 可以得到isa的ISA_BITFIELD信息
(lldb) p/x 0x00000001066cc590 & 0x00007ffffffffff8ULL
///0x00000001066cc590即为当前person对象的类
(unsigned long long) $1 = 0x00000001066cc590
///验证一下
(lldb) po 0x00000001066cc590
LPPerson
///获取LPPerson类内存信息
///方式1
(lldb) x/4g 0x00000001066cc590
0x1066cc590: 0x00000001066cc568 0x00007fff89d0fd00
0x1066cc5a0: 0x0000600002b85900 0x0001801c00000003
///方式2
(lldb) x/4g LPPerson.class
0x1066cc590: 0x00000001066cc568 0x00007fff89d0fd00
0x1066cc5a0: 0x0000600002b85900 0x0001801c00000003
///方式3
(lldb) x/4g object_getClass(person)
0x1066cc590: 0x00000001066cc568 0x00007fff89d0fd00
0x1066cc5a0: 0x0000600002b85900 0x0001801c00000003
////查看类的isa指针
(lldb) po 0x00000001066cc568
LPPerson
///获取类的isa指针的类即元类
(lldb) p/x 0x00000001066cc568 & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x00000001066cc568
(lldb) po 0x00000001066cc568
LPPerson
调试过程中我们发现了三种可以查看类内存信息的方式:
- 直接获取,通过类的内存地址
- 通过类的
class
,例如LPPerson.class
- 利用
runtime
的api
:object_getClass
,例如object_getClass(person)
以及我们可以得到的结论:
- 实例对象
person
的isa
指针的类是LPPerson
这可以说明,实例对象的isa是指向类的 -
LPPerson
类的isa
指针的类还是LPPerson
,这是为什么呢?
类对象也是对象,所以它的isa指针同样也会指向一个类,这个类就叫作元类。这是有苹果定义的
元类的特点:
- 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类
- 元类 是类对象 的类,每个类都有一个独一无二的元类用来存储 类方法的相关信息。
- 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
元类的isa
走向:
接着上面继续调试:
///打印元类的内存信息
(lldb) x/4g 0x00000001066cc568
0x1066cc568: 0x00007fff89d0fcd8 0x00007fff89d0fcd8
0x1066cc578: 0x00006000010aa100 0x0003c03500000007
///获取元类的isa指针
(lldb) po 0x00007fff89d0fcd8
NSObject
///获取元类的isa指针的指向类
(lldb) p/x 0x00007fff89d0fcd8 & 0x00007ffffffffff8ULL
(unsigned long long) $10 = 0x00007fff89d0fcd8
///获取类型
(lldb) po 0x00007fff89d0fcd8
NSObject
///获取NSObject的isa指针
(lldb) x/4g 0x00007fff89d0fcd8
0x7fff89d0fcd8: 0x00007fff89d0fcd8 0x00007fff89d0fd00
0x7fff89d0fce8: 0x00006000002a0e00 0x0009c0310000000f
///获取类型
(lldb) po 0x00007fff89d0fcd8
NSObject
看到这里,我们就可以到结论了:
- 元类的
isa
指向NSObject
,即根元类 - 根元类的
isa
指向NSObject
,即NSObject
的isa
指向自己
总结:
- 对象的
isa
指向类(也可称为类对象) - 类的
isa
指向元类 - 元类的
isa
指向根元类,即NSObject
- 根元类的
isa
指向它自己
类的继承走向
(lldb) po student.superclass
LPPerson
(lldb) po LPPerson.superclass
NSObject
(lldb) po NSObject.superclass
nil
总结:
实例对象继承于类,类对象继承于根类NSObject
,而NSObject
继承于nil
类的唯一性
继续我们的lldb
调试
///获取NSObject的isa指针
(lldb) x/4g 0x00007fff89d0fcd8
0x7fff89d0fcd8: 0x00007fff89d0fcd8 0x00007fff89d0fd00
0x7fff89d0fce8: 0x00006000002a0e00 0x0009c0310000000f
///获取类型
(lldb) po 0x00007fff89d0fcd8
NSObject
///获取NSObject的内存信息
(lldb) x/4g NSObject.class
0x7fff89d0fd00: 0x00007fff89d0fcd8 0x0000000000000000
0x7fff89d0fd10: 0x00006000002b0c00 0x000980100000000f
可以发现这个地方的NSObject
的isa
地址和我们上面LPPerson
的根元类NSObject
的isa
一样,即:
内存中只存在存在一份根元类NSObject
,根元类的元类是指向它自己
同样的我们也可以通过代码验证:
Class class1 = [LPPerson class];
Class class2 = [LPPerson alloc].class;
Class class3 = object_getClass([LPPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p-\n", class1, class2, class3);
///结果:
0x1067f1598-
0x1067f1598-
0x1067f1598-
总结:
从结果中可以看出,打印的地址都是同一个,所以NSObject
只有一份,即NSObject
(根元类)在内存中永远只存在一份。
也即:由于类的信息在内存中永远只存在一份,所以类对象只有一份
对象的isa和继承走位:
image.pngisa
走位总结:
- 实例对象(
Instance of Subclass
)的isa
指向 类(class
) - 类对象(
class
)isa
指向 元类(Meta class
) - 元类(
Meta class
)的isa
指向 根元类(Root metal class
) - 根元类(
Root metal class
) 的isa
指向它自己本身,形成闭环,这里的根元类就NSObject
superclass
走位总结:
类 之间 的继承关系:
- 类(
subClass
) 继承自 父类(superClass
) - 父类(
superClass
) 继承自 根类(RootClass
),此时的根类是指NSObject
- 根类 继承自
nil
,所以根类即NSObject
可以理解为万物起源,即无中生有
元类也存在继承,元类之间的继承关系如下:
- 子类的元类(
metal SubClass
) 继承自 父类的元类(metal SuperClass
) - 父类的元类(
metal SuperClass
) 继承自 根元类(Root metal Class
) - 根元类(
Root metal Class
) 继承于 根类(Root class
),此时的根类是指NSObject
这张图片业堪称业界标杆,isa
走位和继承关系非常清晰,不清楚的同学可以多看几遍。
二、objc_class & objc_object
我们查看Objc
源码会发现两个名字看起来相近的类:objc_class
和 objc_object
,这两个分别是什么呢?
在之前的文章iOS 探索isa中,我们将main.m
利用clang
编译成了main.cpp
。查看源码发现如下代码:
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
我们可以发现NSObject_IMPL
实际就是NSObject
的编译过来的,其中isa
指针是来自于Class
类型,而Class
又是来自于objc_class
结构体,也即是说:所有的对象的isa
指针都来自于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;
以及
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);
}
void clearInfo(uint32_t clear) {
ASSERT(isFuture() || isRealized());
data()->clearFlags(clear);
}
....
第一种在runtime.h
文件中,显示已经被废弃了
第二种在objc-runtime-new.h
文件中,这是最新的代码。同时可以发现,objc_class
是继承于objc_object
的。
objc_class
与 objc_object
关系总结:
- 结构体类型
objc_class
继承自objc_object
类型,其中objc_object
也是一个结构体,且有一个isa
属性,所以objc_class
也拥有了isa
属性 - 每一个对象都有一个
isa
指针,来自于objc_class
,但是objc_class
继承自objc_object
,objc_object
(结构体) 是 当前的 根对象,所以每个对象的isa
指针都来自于objc_object
。
三、类的结构分析
1、内存偏移:
先思考一个问题,定义一个数组,并打印其每个元素的地址:
一般我们都会这样做:
int c[4] = {1, 2, 3, 4};
for (int i = 0; i< 4; i ++) {
NSLog(@"%p", &c[i]);
}
其实我们还可以定义一个数组指针来做:
int c[4] = {1, 2, 3, 4};
int *d = c;
for (int i = 0; i< 4; i ++) {
//NSLog(@"%p", &c[i]);
NSLog(@"%p", d + i);
}
二者结果一样:
2020-09-13 22:37:43.204989+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f2170
2020-09-13 22:37:43.205222+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f2174
2020-09-13 22:37:43.205496+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f2178
2020-09-13 22:37:43.205747+0800 Alloc&init&new[4815:10193773] 0x7ffeef2f217c
我们都知道,数组的地址就等于第一个元素的地址,我们可以看到,打印结果中每一个元素的地址只差4字节。这4个字节也可以称为偏移量
,所以我是不是可以直接通过数组首地址,再加上对应位置的偏移量即可取出对应位置的数据呢?
我们再验证下:
int c[4] = {1, 2, 3, 4};
int *d = c;
for (int i = 0; i< 4; i ++) {
//NSLog(@"%p", &c[i]);
NSLog(@"%d", *(d + i));
}
///结果:
2020-09-13 22:44:17.802263+0800 Alloc&init&new[4876:10198358] 1
2020-09-13 22:44:17.802480+0800 Alloc&init&new[4876:10198358] 2
2020-09-13 22:44:17.802785+0800 Alloc&init&new[4876:10198358] 3
2020-09-13 22:44:17.803658+0800 Alloc&init&new[4876:10198358] 4
完美!接下里我们就愉快的进行类的结构分析了。
2、类的结构分析
旧版本的Objc
源码呢,对于objc_class
结构体的定义比较简单,其内部结构包括:objc_ivar_list
、objc_method_list
、objc_cache
以及 objc_protocol_list
可以很清楚的看到,但是新版本发生了很大的变化,结构和之前完全不一样了,我们就需要借助内存偏移来帮助我们观察objc_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();
}
.....
有用的定义的只有这5个类型,但是真正存放类的结构的只可能在bits
中了,我们需要拿到bits
,就需要计算出前三个属性的内存大小,从而通过偏移量去取到bits
。
接下来,我们来分析下前三个属性的内存大小:
-
ISA
:很简单,8字节 -
superclass
:Class
类型,Class
是由objc_object
定义的,是一个指针,占8字节 -
cache
:结构体,取决于其内部最大属性的内存大小,并不是结构体指针的8字节
所以,重点就是cache_t
了,我们查看下cache_t
的源码:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
// Ensure we have enough bits for the buckets pointer.
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
...
只截取了会影响到cache_t
大小的部分代码:
注意:
static
类型的不影响结构体大小
如果走#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
-
buckets
类型是struct bucket_t *
,是结构体指针类型,占8字节 -
mask
是mask_t
类型,而mask_t
是unsigned int
的别名,占4字节
如果走#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
-
_maskAndBuckets
是uintptr_t
类型,它是一个指针,占8字节 -
_mask_unused
是mask_t
类型,而mask_t
是uint32_t
类型定义的别名,占4字节
_flags
和_occupied
-
_flags
是uint16_t
类型,uint16_t
是unsigned short
的别名,占 2个字节 -
_occupied
是uint16_t
类型,uint16_t
是unsigned short
的别名,占 2个字节
所以,我们可以知道cache_t
的内存大小 = 12 + 2 + 2 = 16字节,即获取bits
只需将首地址的基础上加上isa + superClass + cache = 32
字节即可。
获取bits
同样利用LPPerson
对象:
///获取LPPerson对象首地址
(lldb) p/x LPPerson.class
(Class) $1 = 0x00000001000020e8 LGPerson
///打印
(lldb) x/4g 0x00000001000020e8
0x1000020e8: 0x00000001000020c0 0x0000000100334140
0x1000020f8: 0x000000010032e410 0x0000801000000000
///获取bits ,在0x00000001000020c0基础上增加偏移量32字节,变成0x00000001000020e0
(lldb) p (class_data_bits_t*) 0x00000001000020e0
(class_data_bits_t *) $2 = 0x00000001000020e0
///根据class_rw_t *data()定义的方法获取data
(lldb) p $2.data()
(class_rw_t *) $3 = 0x0000000100a16440
Fix-it applied, fixed expression was:
$2->data()
///打印data中所有数据
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975528
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8e202cd8
}
这样我们就成功取到了bits
数据了,接下里我们看下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
....
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 *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
......
其中也定义了methods
、properties
、protocols
,我们尝试获取一下:
(lldb) p $4.methods()
(const method_array_t) $7 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
(lldb) p $4.properties()
(const property_array_t) $8 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
(lldb) p $4.protocols()
(const protocol_array_t) $9 = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
觉得不错记得点赞哦!听说看完点赞的人逢考必过,逢奖必中。ღ( ´・ᴗ・` )比心