联合体
概念
联合体又称共用体,联合体是由多种类型的变量组成,但多个变量共用一块内存,所以变量之间是互斥的,联合体中的变量只能存在一个有值,如果有别的变量被赋值,那么之前的变量就会被覆盖掉。
案例
union {
char bits;
// 增加代码的可读性,相对于这个联合体就是说这一个字节的各个位都是做什么用的,占用几位,总共是用一个字节而已
struct {
// 位域名 : 位域长
char left: 1;
char right: 1;
char top: 1;
char bottom: 1
} my;
} myUnion;
总结
- 联合体中可以定义多个成员,联合体的大小由最大的成员大小决定
- 联合体的成员公用一个内存,一次只能使用一个成员
- 对某一个成员赋值,会覆盖其他成员的值
- 节省了大量的存储空间, 存储效率更高,可读性更强,可以提高代码的可读性,可以使用位运算提高数据的存储效率。
isa
isa是什么
isa是一个Class 类型的指针,每个实例对象的第一个成员变量就是isa指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。
在OC底层探索(一):alloc、init、new源码分析文章中我们介绍到alloc在开辟完内存后,使用iSA进行与类关联,那这个过程是什么样的呢?今天我们探索一下initInstanceIsa是如何执行的。
isa的结构
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
//初始化isa
initIsa(cls, true, hasCxxDtor);
}
然后点进initIsa 方法:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
//初始化一个isa_t
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
在代码中我们可以发现isa_t newisa(0); 申请了一个isa_t,那isa_t是什么呢?
isa指针的类型是由isa_t定义,从定义中可以看出是通过联合体(union)定义的.
union isa_t { //联合体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,两者是互斥关系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)//位域
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
在源码中我们可以发现isa的联合体中有两个变量,分别是cls和bits。
- cls:是Class类型的指针变量,指向的是对象的类。
- bits:是结构体位域指针。
- ISA_BITFIELD:宏 ISA_BITFIELD,用来定义位域,用于存储类信息及其他信息。
ISA_BITFIELD
ISA_BITFIELD 宏在内部分别定义了arm64位架构(iOS)和x86_64架构(macOS)的掩码和位域.。
(注:图片来自“style_月月”的博客)
我们以__arm64__为例,分别解析一下ISA_BITFIELD的属性:
isa与类的关联
关联
以下是initIsa的源码:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;//使用cls定义isa
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
//初始化一个isa_t
isa_t newisa(0);//初始化isa_t
#if SUPPORT_INDEXED_ISA //使用cls定义
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else//nonpointer
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
在源码中我们可以看到initIsa主要做了两个事:
- 通过cls初始化isa
如果是非nonpointer,代表普通的指针,存储着Class、Meta-Class对象的内存地址信息 - 通过bits初始化isa
如果是nonpointer,则会进行一系列的初始化操作.其中的newisa.shiftcls = (uintptr_t)cls >> 3;中的shiftcls存储着Class、Meta-Class对象的内存地址信息
验证
1.通过 isa & ISA_MSAK
-
ISA_MSAK: arm64中 值为 0x0000000ffffffff8ULL;
x86_64中 值为 0x00007ffffffffff8ULL。 -
在_class_createInstanceFromZone方法,此时cls 与 isa已经关联完成
-
执行po objc查看是否绑定成功
-
执行x/4gx obj,得到isa指针的地址0x001d8001000020e9
-
将isa指针地址 & ISA_MASK (处于macOS,使用x86_64中的宏定义),即 po 0x001d8001000020e9 & 0x00007ffffffffff8 ,得出LGPerson
2.通过位运算
- 在_class_createInstanceFromZone方法,此时cls 与 isa已经关联完成
- 执行x/4gx obj,得到isa指针的地址0x001d8001000020e9,在isa指针中shiftcls存储的是类信息,且shiftcls此时占44位(macOS)。
- 想要读取中间的44位 类信息,就需要经过位运算 ,将右边3位,和左边除去44位以外的部分都抹零,其相对位置是不变的。
位运算步骤:
- 将isa地址右移3位:p/x 0x001d8001000020e9 >> 3 ,得到0x0003b0002000041d
- 在将得到的0x0003b0002000041d左移20位:p/x 0x0003b0002000041d << 20,得到0x0002000041d00000
- 将得到的0x0002000041d00000 再右移17位:p/x 0x0002000041d00000 >> 17得到新的0x00000001000020e8
- 获取cls的地址 与 上面的进行验证 :p/x cls 也得出0x00000001000020e8,所以由此可以证明 cls 与 isa是关联的
isa指向走位
(注:虚线代表isa;实线代表继承)
解析:
- Isa:对象的isa指向i类对象,类对象的isa指向i元类,元类的isa指向i根元类(NSObject的元类) ,根元类(NSObject的元类) 的isa指向i自己。
- 继承:类对象的继承指向 父类对象,父类对象的继承指向 NSObject,NSObject的继承指向nil;
- 元类的继承指向 父类元类,父类元类的继承指向 根元类(NSObject的元类),根元类(NSObject的元类)的继承指向nil;
元类
有上述我们知道了isa继承的走向,类对象的isa指向了元类,那元类是个什么东西呢?
- 类在OC中其实也是一个对象,称之为类对象,其isa的位域执行苹果定义的元类;
- 元类是系统生成的,其定义和创建都是由编译器完成的,在这个过程中,类的归属来自于元类;
- 元类是类对象的类,每个类都有一个独一无二的元类,用来存储类方法等相关信息;
**问题 **:
Student继承于Person,那么Student类型的对象student与Person类型的对象person是什么关系?
答案是:没有任何关系!
类是可以继承的,但是对象之间是没有任何关系的。
验证
准备工作
- 创建类Person,继承于NSObject:
.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
// @property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end
- 创建Student类,继承于Person
.h文件
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Student : Person
@end
- 在main.m文件中初始化Person和Student
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Person.h"
#import "Student.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
//0x00007ffffffffff8ULL
Person *person = [Person alloc];
Student *student = [Student alloc];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
验证person 的isa走位
通过lldb命令打印探索isa的走位:
验证student的isa走位
验证类对象的继承
验证元类的继承
总结
- isa走向:对象 -> 类对象 -> 元类 -> 根元类(NSObject的元类) -> 自己(根元类)
- 继承:类对象 -> 父类对象 -> NSObject -> nil
元类 –> 父类元类 ->根元类(NSObject的元类) -> NSObject -> nil;