iOS內(nèi)存管理(一)、內(nèi)存分區(qū)和引用計(jì)數(shù)

iOS內(nèi)存管理(一)、內(nèi)存分區(qū)和引用計(jì)數(shù)
iOS內(nèi)存管理(二)alloc、retain、release、dealloc

一、內(nèi)存分區(qū)

內(nèi)存布局

0xc0000000轉(zhuǎn)化出來(lái),正好為3GB,所以我們的運(yùn)行內(nèi)存最多為3GB
在動(dòng)態(tài)分配內(nèi)存的時(shí)候,棧區(qū)的棧幀不斷往下走,而堆區(qū)隨著內(nèi)存開(kāi)辟越多會(huì)不斷往上走,當(dāng)它們重合的時(shí)候,就形成了堆棧溢出。

在dyld加載可執(zhí)行文件到內(nèi)存的時(shí)候,它會(huì)將加載的數(shù)據(jù)給分別存放到.bss、.data、.text段。

iOS的內(nèi)存分區(qū)指RAM中的內(nèi)存分區(qū),它主要分為五大區(qū):

內(nèi)存分區(qū)

二、內(nèi)存管理方案 isa_t

TaggedPointer:小對(duì)象-NSNumber,NSDate以及長(zhǎng)度短的NSSTring
NONPOINTER_ISA:非指針型isa
散列表:引用計(jì)數(shù)表、弱引用表

在這里我們先介紹一下isa,它在源碼中的結(jié)構(gòu)為(只看arm64的)

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
      //0表示普通的isa指針,1表示使用優(yōu)化,存儲(chǔ)引用計(jì)數(shù)
      uintptr_t nonpointer        : 1;                                       \
      //表示該對(duì)象是否包含關(guān)聯(lián)對(duì)象,如果沒(méi)有,則析構(gòu)時(shí)會(huì)更快
      uintptr_t has_assoc         : 1;                                       \
      //表示該對(duì)象是否有C++或ARC的析構(gòu)函數(shù),如果沒(méi)有,則析構(gòu)時(shí)更快
      uintptr_t has_cxx_dtor      : 1;                                       \
      //類的指針
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      //固定值為0xd2,用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
      uintptr_t magic             : 6;                                       \
      //表示該對(duì)象是否有過(guò)weak對(duì)象,如果沒(méi)有,則析構(gòu)時(shí)更快
      uintptr_t weakly_referenced : 1;                                       \
      //表示該對(duì)象是否正在析構(gòu)
      uintptr_t deallocating      : 1;                                       \
      //表示該對(duì)象的引用計(jì)數(shù)值是否過(guò)大需要額外存儲(chǔ)到sidetable中
      uintptr_t has_sidetable_rc  : 1;                                       \
      //存儲(chǔ)最多2^8-1的引用計(jì)數(shù)值,存儲(chǔ)sidetable以外的引用計(jì)數(shù)值減1的結(jié)果
      uintptr_t extra_rc          : 19
    };
};

三、TaggedPointer

TaggedPointer常用來(lái)存儲(chǔ)小對(duì)象如NSNumber,NSDate以及長(zhǎng)度短的NSSTring,TaggedPointer指針的值不再是地址了,而是真正的值,所以實(shí)際上它不再是一個(gè)對(duì)象了,它只是披著對(duì)象皮的普通變量而已!所以,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free
TaggedPointer內(nèi)存讀取是讀取對(duì)象的3倍,創(chuàng)建對(duì)象過(guò)程是創(chuàng)建對(duì)象的106倍
一般TaggedPointer的打印的結(jié)構(gòu)為tag+值+值類型

源碼中,如果為taggedPointer,直接返回非類類型

inline bool objc_object::isClass()
{
    //如果是TaggedPointer,返回false
    if (isTaggedPointer()) return false;
    return ISA()->isMetaClass();
}

inline bool objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}

static inline bool _objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
define _OBJC_TAG_MASK (1UL<<63)

_objc_isTaggedPointer函數(shù)中,將自身指針值與1...0進(jìn)行按位與操作,如果還是1...0,則是使用了taggedPointer機(jī)制,也就是說(shuō),在iOS中,判斷是否taggedPoint,就是看其最高位是否為1。
拓展:在MACOS中,由于define _OBJC_TAG_MASK 1UL,所以,判斷在MACOS中是否為taggedPoint,就是看其最低位是否為1

我們?cè)倏催@個(gè)函數(shù)

static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
    if (tag <= OBJC_TAG_Last60BitPayload) {
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    } else {
        uintptr_t result =
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    }
}

我們不看其它部分,最終不論是哪一種形式,最后函數(shù)都會(huì)調(diào)用_objc_encodeTaggedPointer(result),我們?cè)賮?lái)看這個(gè)函數(shù)

uintptr_t objc_debug_taggedpointer_obfuscator = 0;
//編碼方法
static inline void * _Nonnull_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
//解碼方法
static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

編碼方法中,與0進(jìn)行按位異或進(jìn)行編碼,解碼方法中,同樣與0進(jìn)行按位異或進(jìn)行解碼

在最新版的objc4-781源碼中,關(guān)于objc_debug_taggedpointer_obfuscator進(jìn)行更多一步的操作,在_read_images方法中,調(diào)用initializeTaggedPointerObfuscator()對(duì)objc_debug_taggedpointer_obfuscator進(jìn)行了初始化(做了代碼混淆),我們看看這個(gè)函數(shù)的源碼:

static void
initializeTaggedPointerObfuscator(void)
{
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || DisableTaggedPointerObfuscation) {
        //老版本直接為0
        objc_debug_taggedpointer_obfuscator = 0;
    } else {
        //新版本給一個(gè)隨機(jī)數(shù),和_OBJC_TAG_MASK按位取反后的值進(jìn)行與操作
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
    }
}
define _OBJC_TAG_MASK (1UL<<63)

所以按照現(xiàn)在的源碼結(jié)構(gòu),我們?nèi)绻胍吹皆瓉?lái)的tag+值+值類型結(jié)構(gòu),需要我們將_objc_decodeTaggedPointer方法拷貝出來(lái),自己對(duì)它進(jìn)行解碼操作,



- (void)taggedPointerTest{
    int num1 = 22;
    long num2 = 55;
    float num3 = 2.0f;
    double num4 = 4.0;
    double num5 = 22.33;
    NSNumber *number1 = @(num1);
    NSNumber *number2 = @(num2);
    NSNumber *number3 = @(num3);
    NSNumber *number4 = @(num4);
    NSNumber *number5 = @(num5);
    
    NSLog(@"所屬類:%@---指針:%p---值:%@---0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer(number1));
    NSLog(@"所屬類:%@---指針:%p---值:%@---0x%lx",object_getClass(number2),number2,number2,_objc_decodeTaggedPointer(number2));
    NSLog(@"所屬類:%@---指針:%p---值:%@---0x%lx",object_getClass(number3),number3,number3,_objc_decodeTaggedPointer(number3));
    NSLog(@"所屬類:%@---指針:%p---值:%@---0x%lx",object_getClass(number4),number4,number4,_objc_decodeTaggedPointer(number4));
    NSLog(@"所屬類:%@---指針:%p---值:%@---0x%lx",object_getClass(number5),number5,number5,_objc_decodeTaggedPointer(number5));

}
extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t
_objc_decodeTaggedPointer(id ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

//打印結(jié)果
// 所屬類:__NSCFNumber---指針:0xfd8919a30310b479---值:22---0xb000000000000162
// 所屬類:__NSCFNumber---指針:0xfd8919a30310b668---值:55---0xb000000000000373
// 所屬類:__NSCFNumber---指針:0xfd8919a30310b53f---值:2---0xb000000000000024
// 所屬類:__NSCFNumber---指針:0xfd8919a30310b55e---值:4---0xb000000000000045
// 所屬類:__NSCFNumber---指針:0x6000013d0d80---值:22.33---0x4d8979a3022db89b

我們可以看到指針打印出來(lái)的值已經(jīng)沒(méi)有原來(lái)的那種含義,因?yàn)楝F(xiàn)在源碼中已做了代碼混淆。而在我們自己解碼出來(lái)的值中,可以看到最后一位代表它的類型,2表示int類型,3代表long類型,4代表float類型,5代表double類型,而對(duì)于復(fù)雜的浮點(diǎn)數(shù),如num5,最后一位并不能確定其類型。除此以外,在類型前面則顯示的是它的值的16進(jìn)制值。

NONPOINTER_ISA

若isa_t中位域中的nonpointer為1,表示優(yōu)化過(guò)的isa,用于存儲(chǔ)引用計(jì)數(shù)

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
      //0表示普通的isa指針,1表示使用優(yōu)化,存儲(chǔ)引用計(jì)數(shù)
      uintptr_t nonpointer        : 1;                                       \
      //表示該對(duì)象是否包含關(guān)聯(lián)對(duì)象,如果沒(méi)有,則析構(gòu)時(shí)會(huì)更快
      uintptr_t has_assoc         : 1;                                       \
      //表示該對(duì)象是否有C++或ARC的析構(gòu)函數(shù),如果沒(méi)有,則析構(gòu)時(shí)更快
      uintptr_t has_cxx_dtor      : 1;                                       \
      //類的指針
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      //固定值為0xd2,用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
      uintptr_t magic             : 6;                                       \
      //表示該對(duì)象是否有過(guò)weak對(duì)象,如果沒(méi)有,則析構(gòu)時(shí)更快
      uintptr_t weakly_referenced : 1;                                       \
      //表示該對(duì)象是否正在析構(gòu)
      uintptr_t deallocating      : 1;                                       \
      //表示該對(duì)象的引用計(jì)數(shù)值是否過(guò)大需要額外存儲(chǔ)到sidetable中
      uintptr_t has_sidetable_rc  : 1;                                       \
      //存儲(chǔ)最多2^8-1的引用計(jì)數(shù)值,存儲(chǔ)sidetable以外的引用計(jì)數(shù)值減1的結(jié)果
      uintptr_t extra_rc          : 19
    };
};

現(xiàn)在我們來(lái)研究retainCount在源碼中的實(shí)現(xiàn)

- (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}

uintptr_t _objc_rootRetainCount(id obj)
{
    //判斷對(duì)象是否為空,為空斷在這里
    ASSERT(obj);
    return obj->rootRetainCount();
}
inline uintptr_t objc_object::rootRetainCount()
{
    //如果是taggedPointer 直接返回指針值
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    //如果是優(yōu)化后的指針(存儲(chǔ)引用計(jì)數(shù))
    if (bits.nonpointer) {
        //將extra_rc值加1,rc是引用計(jì)數(shù)
        uintptr_t rc = 1 + bits.extra_rc;
        //如果有sidetable
        if (bits.has_sidetable_rc) {
            //獲取sidetable中存儲(chǔ)的引用計(jì)數(shù)值
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    //如果不是優(yōu)化后的指針,那么引用計(jì)數(shù)從sidetable中返回
    return sidetable_retainCount();
}

//從sideTable中取出引用計(jì)數(shù)值,不上鎖,而且不加1,因?yàn)樵谇懊娴暮瘮?shù)中已經(jīng)+1
size_t objc_object::sidetable_getExtraRC_nolock()
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    //從refcnts中取出值,然后右移兩位獲取到引用計(jì)數(shù)
    //這里為什么要右移兩位,因?yàn)樽畹臀淮鎯?chǔ)了是否有弱引用,低二位存儲(chǔ)了SideTable是否在析構(gòu),所以需要右移兩位
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

//從sideTable中取出引用計(jì)數(shù)值,上鎖,而且要加1
uintptr_t objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        //從refcnts中取出值,然后右移兩位獲取到引用計(jì)數(shù)
        //這里為什么要右移兩位,因?yàn)樽畹臀淮鎯?chǔ)了是否有弱引用,低二位存儲(chǔ)了SideTable是否在析構(gòu),所以需要右移兩位
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1) 
#define SIDE_TABLE_RC_ONE            (1UL<<2) 
#define SIDE_TABLE_RC_SHIFT 2

//SideTable的數(shù)據(jù)結(jié)構(gòu)
struct SideTable {
    //鎖
    spinlock_t slock;
    //存儲(chǔ)了引用計(jì)數(shù)的哈希表
    RefcountMap refcnts;
    //弱引用表
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }
    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

//SideTable中RefcountMap的定義,它是一張哈希表
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

RefcountMap refcnts:它是一張哈希表,查詢的時(shí)候使用對(duì)象的地址作為key,經(jīng)過(guò)哈希算法,得到一個(gè)值,這個(gè)值的最低位存儲(chǔ)了是否有弱引用,低二位存儲(chǔ)了是否正在析構(gòu),將這個(gè)值右移兩位,如果nonpointer為0,右移兩位后加1,則是引用計(jì)數(shù)值;如果nonpointer為1,我們從isa_t的extra_rc中取出值,加1,再加上右移兩位的值,則得到了引用計(jì)數(shù)值

從上面的源碼中,對(duì)于非TaggedPointer對(duì)象,我們可以得出以下結(jié)論:

  • 如果nonpointer為0,表示isa未優(yōu)化過(guò),不作為存儲(chǔ)引用計(jì)數(shù),那么引用計(jì)數(shù)值都存放在SideTable中成員refcnts中,從refcnts中取出值加1,則得到了我們的引用計(jì)數(shù)值
  • 如果nonpointer為1,表示isa優(yōu)化過(guò),存儲(chǔ)了部分引用計(jì)數(shù)值,我們從isa_t的extra_rc中取出值,加1,再?gòu)腟ideTable中成員refcnts中取出值右移兩位,加上前面的值,則得到了引用計(jì)數(shù)值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容