为什么用联合体体位域(union) ?因为它可以很大程度节省空间。举例创一个Robot类
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Robot : NSObject
@property (nonatomic, assign) int front;
@property (nonatomic, assign) int back;
@property (nonatomic, assign) int left;
@property (nonatomic, assign) int right;
@end
NS_ASSUME_NONNULL_END
在main文件中实现
#import <Foundation/Foundation.h>
#import <malloc/malloc.h>
#import <objc/runtime.h>
#import "Robot.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Robot *robot = [[Robot alloc] init];
robot.front = 1;
robot.back = 0;
robot.left = 0;
robot.right = 1;
NSLog(@"%@ - %lu - %lu - %lu",robot,sizeof(robot),class_getInstanceSize([Robot class]),malloc_size((__bridge const void *)(robot)));
NSLog(@"Hello, World!");
}
return 0;
}
查看打印的结果:
2021-03-25 17:31:34.851642+0800 001-联合体位域[4015:311271] <Robot: 0x101db9140> - 8 - 24 - 32
这里sizeof(robot) = 8,这里打印的是robot的类型大小,结构体指针大小为8个字节没有问题;class_getInstanceSize([Robot class]) = 24,这里class_getInstanceSize获取的是对象需要的真正的内存,这里是24个字节.这只是4个属性,如果再增加些属性呢?而如果我们用位域的话,就可以节省很多的空间,这里每个属性都是互斥的,用位域也更合理
这里贴出我的代理Robot.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Robot : NSObject
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
- (void)setFront:(BOOL)isFront;
- (BOOL)isFront;
- (void)setBack:(BOOL)isBack;
- (BOOL)isBack;
@end
NS_ASSUME_NONNULL_END
下面是Robot.m 文件的实现
#import "Robot.h"
#define DirectionFrontMask (1 << 0)
#define DirectionBackMask (1 << 1)
#define DirectionLeftMask (1 << 2)
#define DirectionRightMask (1 << 3)
@interface Robot(){
// 联合体
union {
char bits;
struct {
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
} _direction;
}
@end
@implementation Robot
- (instancetype)init
{
self = [super init];
if (self) {
_direction.bits = 0b0000000000;
}
return self;
}
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= DirectionFrontMask;
} else {
_direction.bits |= ~DirectionFrontMask;
}
NSLog(@"%s",__func__);
}
- (BOOL)isFront{
return _direction.bits & DirectionFrontMask;
// return _direction.front;
}
- (void)setBack:(BOOL)isBack {
_direction.back = isBack;
NSLog(@"%s",__func__);
}
- (BOOL)isBack{
return _direction.bits >>1 & DirectionFrontMask;
// return _direction.back;
}
@end
这里定义了一个union,属性分别占了1个位置,我们定义的时候也可以根据自己实际需要定义大小
union {
char bits;
struct {
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
} _direction;
set/get方法的实现,原理是位运算
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= DirectionFrontMask;
} else {
_direction.bits |= ~DirectionFrontMask;
}
NSLog(@"%s",__func__);
}
- (BOOL)isFront{
return _direction.bits & DirectionFrontMask;
// return _direction.front;
}
通过位运算我们可以得到各个位置的属性值,例如第二个位置的值,我们可以把值右移一位并与 1与运算就得到的第二个位置的值
而isa的结构也是类似这样的。我们在研究alloc的流程时,有看到这样的代码:
这里initIsa我们知道是把开辟的地址与类关联起来的作用。我们看一下源码是怎么样关联的。贴出源码:
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
// 根据nonpointer 的值判断返回cls 还是 new一个新的isa
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
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;
}
}
可以看到在initIsa里面,我们看到isa的初始化方法用到的isa_t,找到它的结构是这样的
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
可以看到跟我们上面自己定义的union即为类似。查看下ISA_BITFIELD的宏定义:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
看到根据是真机还是模拟器区分,属性站的位置大小有区分,shiftcls在arm64占用的是从第三个位置开始的33个位置,而在x86_64占用的是在第三个位置开始的44个位置,而且我们也在上面initIsa函数里看到isa_t中的Class cls;与uintptr_t bits;必然是互斥的
打断点在 isa_t newisa(0); 之后打印下newisa的内容,可以看到cls = nil,shiftcls = 0
在newisa.bits赋值后,打印下newisa的内容,可以看到cls ,bits, shiftcls 都有值了
这时magic = 59,为什么呢? newisa.bits = ISA_MAGIC_VALUE;这时默认赋值。查看宏定义 # define ISA_MAGIC_VALUE 0x001d800000000001ULL,转成16进制可以看到
可以看到0号位置是1,代表nonpointer = 1,1-46位置都是0,而第1个位置是has_assoc=0,第2个位置has_cxx_dtor = 0,3-46号位是shiftcls都是0,下面是magic占6个位置,分别是111011 ,计算出来是59.对应上面图里的 magic = 59。
走到newisa.shiftcls = (uintptr_t)cls >> 3;下面打印下newisa: 可以看到cls 变成了 LGPerson,shiftcls 也变成了 536875038,那么右移三位是为什么呢?这里是因为shiftcls在bit里的第3个位置之后,所以把(uintptr_t)cls右移3位赋值给newisa.shiftcls,通过这样与cls = Person 相关联。
而在shiftcls赋值之前(uintptr_t)cls的值为 4295000304
再从get的方式看下,贴出源码
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
上图可以看到获取isa的函数最终返回的是(Class)(isa.bits & ISA_MASK); 是isa.bits 与上ISA_MASK 再强制转换为Class类型,而与的过程其实也就是左移右移各种操作。这里为什么要强制转化呢?可以看到获取isa的函数的返回值都是Class
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}