iOS底层探索四(isa初探-联合体,位域,内存优化)
前言
相关文章:
iOS底层探索一(底层探索方法)
相关代码:
objc4_752源码 UnionDomain
前几篇文章对alloc方法进行了初步探究,
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
/**
fastpath(x)表示x很可能不为0,希望编译器进行优化;
slowpath(x)表示x很可能为0,希望编译器进行优化——这里表示cls大概率是有值的,编译器可以不用每次都读取 return nil 指令
*/
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
/**
实际意义是hasCustomAllocWithZone——这里表示有没有alloc / allocWithZone的实现(只有不是继承NSObject/NSProxy的类才为true)
*/
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
/**
内部调用了bits.canAllocFast默认为false,可以自行点击进入查看
*/
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
//底层进行开辟控件
/*
其中calloc函数意思是开辟一个内存空间,cls->bits.fastInstanceSize() 意思是开辟一个cls类的内存空间的大小,前面__count意思是倍数,其中cls->bits.fastInstanceSize()大小是遵循内存对齐原则开辟内存的
*/
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
今天我们继续探索alloc方法中的initInstanceIsa方法进行继续探索
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
这个里面的assert方法为断言方法(这里可以不用理会想要了解可以点击查看)
这里进入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 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的结构
//联合体
union isa_t {
isa_t() { } //初始化方法
isa_t(uintptr_t value) : bits(value) { }
Class cls; //绑定类
uintptr_t bits; //typedef unsigned long长整形8字节
#if defined(ISA_BITFIELD)
struct {
// 这里使用宏定义的原因是因为要根据系统架构进行区分的
ISA_BITFIELD; // defined in isa.h 位域
};
#endif
};
这个结构为union(联合体),查看下位域的声明;
# 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
/**
nonpointer :1
表示是否对isa指针开启指针优化;0代表纯isa指针,1代表不止是类对象指针,还包含了类信息、对象的引用计数等;
has_cxx_dtor:1
该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;
hasCxxDtor:1
该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;
shiftcls:33
存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针;
magic:6
用于调试器判断当前对象是真的对象还是没有初始化的空间;
weakly_referenced :1
标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放;
deallocating :1
标志对象是否正在释放内存;
has_sidetable_rc :1
当对象引用计数大于10时,则需要借用该变量存储进位
extra_rc :19
当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9.如果引用计数大于10,则需要使用上面提到的has_sidetable_rc。
**/
上述中我们可以看到 isa_t
类型是一个 union
联合体。ISA_BITFIELD
是位域。
联合体(union)
当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union)。在C Programming Language 一书中对于联合体是这么描述的:
1)联合体是一个结构;
2)它的所有成员相对于基地址的偏移量都为0;
3)此结构空间要大到足够容纳最"宽"的成员;
4)其对齐方式要适合其中所有的成员;
位域
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便。
这么描述可能有点苍白了,我们可以使用一个demo来进行描述下,这里新建一个Object-C工程,新建一个类亚瑟(XZArthur)
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XZArthur : NSObject
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@end
@implementation XZArthur
@end
- (void)viewDidLoad {
[super viewDidLoad];
XZArthur *arthur = [[XZArthur alloc]init];
arthur.front = YES;
arthur.back = YES;
arthur.right = YES;
arthur.left = YES;
NSLog(@"联合体--位域");
}
如果是这样的话,我们可以在NSLog处打断点,LLDB调试看下内存情况
01 01 01 01 00 00 00 00 这里至少会使用4个字节存这个4个属性
这里我们就对属性对空间来说就有些浪费
我们可以使用char类型进行表示
char 0000 0001 (这里是用二进制表示的)
我们可以使用第一位为1标示向前,第二位为1标示向后,第三位为0标示向左,第四位为1标示向右,这样我们就只用了4个位置(4个位置二进制中标示为8),不到一个字节,这样就大大的节省了内存空间
由此我们就发现如果直接使用属性会很浪费空间,所以我们这里就可以使用联合体来标示这几个位置
即为,我们这里只用一个char类型,让其中的属性共用内存,共用内存大小为,联合体中最大的那个内存
我没在XZArthur类中进行修改
#import "XZArthur.h"
#define XZDirectionFrontMask (1 << 0) //1左移0位为1
#define XZDirectionBackMask (1 << 1) //1往左移1位为2
#define XZDirectionLeftMask (1 << 2) //1往左移2位为4
#define XZDirectionRightMask (1 << 3) //1往左移2位为8
@interface XZArthur(){
union {
// 联合体
char bits;
} _direction;
}
@end
- (instancetype)init
{
self = [super init];
if (self) {
// 这里就标示为二进制的全部为0
_direction.bits = 0b0000000000;
}
return self;
}
/**
0000 0000 原始值
0000 0001 传进来值
| 进行或运算
0000 0001 计算结果值为1
0000 0000 原始值
1111 1110 XZDirectionFrontMask取非值
& 进行与运算算
0000 0000 计算结 果值为0
*/
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= XZDirectionFrontMask;
} else {
_direction.bits &= ~XZDirectionFrontMask;
}
}
- (BOOL)isFront{
return !!(_direction.bits & XZDirectionFrontMask);
}
@end
这样我就就使用联合体对4个属性进行了表示,这里我们就已经使用联合体对属性进行替换了,已经节省内存了,那位域是用来干什么呢?
我们现在是已经使用联合体表示了这几功能,那我们怎么让别人理解起来更加方便呢,或者说,这里面属性如果增加了呢,就需要使用位域来进行进一步的描述了,我们可以将属性进行扩增,也可以很清晰的看出来属性的意义,包括所占大小;这里所占内存大小可直接从位域中可以看出,也可以根据char类型进行看出,char为1字节
#define XZDirectionFrontMask (1 << 0) //1左移0位为1
#define XZDirectionBackMask (1 << 1) //1往左移1位为2
#define XZDirectionLeftMask (1 << 2) //1往左移2位为4
#define XZDirectionRightMask (1 << 3) //1往左移2位为8
#define XZDirectionBigRecruitMask (1 << 4) //1往左移7位
@interface XZArthur(){
union {
// 联合体
char bits;
// 位域
struct {
char front : 1; //占一位
char back : 1; //占1位
char left : 1; //占1位
char right : 1; //占1位
char bigRecruit : 8; //占8位
};
} _direction;
}
@end
@implementation XZArthur
- (instancetype)init
{
self = [super init];
if (self) {
// 这里就标示为二进制的全部为0
_direction.bits = 0b0000000000;
}
return self;
}
/**
0000 0000 原始值
0000 0001 传进来值
| 进行或运算
0000 0001 计算结果值为1
0000 0000 原始值
1111 1110 XZDirectionFrontMask取非值
& 进行与运算算
0000 0000 计算结 果值为0
*/
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= XZDirectionFrontMask;
} else {
_direction.bits &= ~XZDirectionFrontMask;
}
}
- (BOOL)isFront{
return !!(_direction.bits & XZDirectionFrontMask);
}
- (void)setBack:(BOOL)isBack {
_direction.back = isBack;
}
- (BOOL)isBack{
return _direction.back;
}
- (void)setRight:(BOOL)right
{
_direction.right = right;
}
- (BOOL)isRight
{
return _direction.right;
}
- (void)setLeft:(BOOL)left
{
_direction.left = left;
}
- (BOOL)isLeft
{
return _direction.left;
}
@end
从上述代码中我们可以看出,给bit中赋值的时候可以使用2种方式,1.直接对bit进行位运算赋值,2.直接使用位域中的属性进行赋值都可以;
我们可以在lldb中进行验证一下:
我们可以看到只是用0f,就表示了4个属性的值;
这就是我对联合体和位域的解释!
然后我们再回到isa的源码中
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
//isTaggedPointer这个涉及内存管理,后续在进行详细解释
assert(!isTaggedPointer());
if (!nonpointer) {
// isa对类的绑定
// 使用位域属性进行赋值
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
// # define ISA_INDEX_MAGIC_VALUE 0x001C0001 直接使用bit进行赋值
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
// # define ISA_MAGIC_VALUE 0x000001a000000001ULL 直接使用bit进行赋值
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;
}
}
总结
以上就是关于alloc探索,到initInstanceIsa的方法内部实现机制,如果有错误的地方还请指正,大家一起讨论,开发水平一般,还望大家体谅。
写给自己:
有人帮你,是你的幸运;无人帮你,是公正的命运。没有人该为你做什么,因为生命是你自己的,你得为自己负责,继续努力,未完待续...