iOS底層探索四(isa初探-聯合體,位域,內存優化)

前言   

   
    相關文章:   
      iOS底層探索一(底層探索方法)       

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

       iOS底層探索三(內存對齊與calloc分析)    

       iOS底層探索五(isa與類的關係)  

iOS底層探索六(類的分析上)

iOS底層探索七(類的分析下)

iOS底層探索八(方法本質上)

iOS底層探索九(方法的本質下objc_msgSend慢速及方法轉發初探)

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;
    }
}

Struct 與 Union 主要有以下區別:

  • 1. struct 和 union 都是由多個不同的數據類型成員組成, 但在任何同一時刻, union 中只存放了一個被選中的成員, 而struct的所有成員都存在。在 struct中,各成員都佔有自己的內存空間,它們是同時存在的。一個struct變量的總長度等於所有成員長度之和。在Union中,所有成員不能同時佔用它的內存空間,它們不能同時存在。Union變量的長度等於最長的成員的長度。
  • 2. 對於union的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於struct的不同成員賦值是互不影響的。

總結

    以上就是關於alloc探索,到initInstanceIsa的方法內部實現機制,如果有錯誤的地方還請指正,大家一起討論,開發水平一般,還望大家體諒,歡迎大家點贊,關注我的CSDN,我會定期做一些技術分享!

寫給自己:

   有人幫你,是你的幸運;無人幫你,是公正的命運。沒有人該爲你做什麼,因爲生命是你自己的,你得爲自己負責,繼續努力,未完待續...

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章