欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS底层探索四(isa初探-联合体,位域,内存优化)

程序员文章站 2022-04-12 23:06:10
...

前言   

   
    相关文章:   
      iOS底层探索一(底层探索方法)       

       iOS底层探索二(OC 中 alloc 方法 初探)

       iOS底层探索三(内存对齐与calloc分析)      

    相关代码:
      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调试看下内存情况

iOS底层探索四(isa初探-联合体,位域,内存优化)

 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中进行验证一下:

iOS底层探索四(isa初探-联合体,位域,内存优化)

我们可以看到只是用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的方法内部实现机制,如果有错误的地方还请指正,大家一起讨论,开发水平一般,还望大家体谅。

写给自己:

   有人帮你,是你的幸运;无人帮你,是公正的命运。没有人该为你做什么,因为生命是你自己的,你得为自己负责,继续努力,未完待续...