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

前言   

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

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

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

       iOS底层探索五(isa与类的关系)  

iOS底层探索六(类的分析上)

iOS底层探索七(类的分析下)

iOS底层探索八(方法本质上)

iOS底层探索九(方法的本质下objc_msgSend慢速及方法转发初探)

iOS底层探索十(方法的本质下-消息转发流程)

相关代码:

      objc4_752源码      UnionDomain

扫描二维码关注公众号,回复: 11195979 查看本文章

      前几篇文章对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;
    }
}

Struct 与 Union 主要有以下区别:

  • 1. struct 和 union 都是由多个不同的数据类型成员组成, 但在任何同一时刻, union 中只存放了一个被选中的成员, 而struct的所有成员都存在。在 struct中,各成员都占有自己的内存空间,它们是同时存在的。一个struct变量的总长度等于所有成员长度之和。在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度。
  • 2. 对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的。

总结

    以上就是关于alloc探索,到initInstanceIsa的方法内部实现机制,如果有错误的地方还请指正,大家一起讨论,开发水平一般,还望大家体谅,欢迎大家点赞,关注我的CSDN,我会定期做一些技术分享!

写给自己:

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

原创文章 88 获赞 21 访问量 2万+

猜你喜欢

转载自blog.csdn.net/ZhaiAlan/article/details/104689711