內(nèi)存管理

內(nèi)存布局

內(nèi)存布局.png
  • stack(棧區(qū)): 方法調(diào)用
  • heap(堆區(qū)):通過(guò)alloc等分配的對(duì)象
  • bss:未初始化的全局變量或靜態(tài)變量等。
  • data:已初始化的全局變量等。
  • text:程序的代碼段

內(nèi)存管理方案

iOS是如何對(duì)內(nèi)存進(jìn)行管理的?

  • TaggedPointer:對(duì)一些小對(duì)象如NSNumber

  • NONPOINTER_ISA: 對(duì)于64位架構(gòu)下的應(yīng)用程序

    在64位架構(gòu)下,isa指針占用64位bit,實(shí)際有32位或者40位就夠用了,剩余的實(shí)際上是浪費(fèi)的,蘋果為了提高內(nèi)存利用率,在這些剩余的bit位當(dāng)中,存儲(chǔ)了一些關(guān)于內(nèi)存管理的相關(guān)數(shù)據(jù)內(nèi)容,所以稱為非指針型的isa

  • 散列表

    散列表是一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu),其中包含了應(yīng)用計(jì)數(shù)表和弱引用計(jì)數(shù)表。

NONPOINTER_ISA結(jié)構(gòu)

arm64架構(gòu)

nonpointer_isa01.png
nonpointer_isa02.png
  • 第0號(hào)位是indexed的標(biāo)志位,如果這個(gè)位置是0,代表的是我們使用的isa指針只是一個(gè)純的isa指針,它里面的內(nèi)容就直接代表了當(dāng)前對(duì)象的類對(duì)象的地址;如果這個(gè)位置是1,就代表這個(gè)isa指針里面存儲(chǔ)的不僅是他的類對(duì)象的地址,而且還有一些內(nèi)存管理方面的數(shù)據(jù)。

  • 第1號(hào)位has_assoc是表示當(dāng)前對(duì)象是否有關(guān)聯(lián)對(duì)象,0沒(méi)有,1有。

  • 第2位has_cxx_dtor,表示的是當(dāng)前對(duì)象是否有使用到C++相關(guān)的一些代碼,或者C++語(yǔ)言方面的一些內(nèi)容。在ARC中也可以通過(guò)這個(gè)標(biāo)志位,來(lái)表示有些對(duì)象是通過(guò)ARC來(lái)進(jìn)行內(nèi)存管理的。

  • 后面的3-35位shiftcls,表示當(dāng)前對(duì)象的類對(duì)象指針地址。

  • 后面的6位是一個(gè)magic字段

  • 后面是一位weakly_referenced,標(biāo)識(shí)這個(gè)對(duì)象是否有相應(yīng)的弱引用指針。

  • deallocating,表示的是當(dāng)前對(duì)象是否在進(jìn)行dealloc操作

  • has_sidetable_rc,表示的是當(dāng)前這個(gè)isa指針當(dāng)中,如果所存儲(chǔ)的引用計(jì)數(shù)已經(jīng)達(dá)到了上限的話,需要外掛一個(gè)sidetable數(shù)據(jù)結(jié)構(gòu),去存儲(chǔ)相關(guān)的引用計(jì)數(shù)內(nèi)容(也就是散列表)

  • extra_rc額外的引用計(jì)數(shù),當(dāng)我們引用計(jì)數(shù)在一個(gè)很小的值得范圍之內(nèi)就會(huì)存到isa指針當(dāng)中,而不是由單獨(dú)的引用計(jì)數(shù)表去存他的引用計(jì)數(shù)。

散列表方式

SideTables()源碼
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
SideTables()結(jié)構(gòu)
sideTable.png

side tables實(shí)際上是一個(gè)hash表,通過(guò)一個(gè)對(duì)象指針,找到他對(duì)應(yīng)的引用計(jì)數(shù)表,或弱引用表。

Side Table

SideTable源碼
struct SideTable {
    spinlock_t slock;
    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(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

SideTable結(jié)構(gòu)

sideTable02.png

為什么不是一個(gè)side table?

one_side_table03.png

假如說(shuō)只有一張side table,相當(dāng)于我們?cè)趦?nèi)存當(dāng)中分配的所有對(duì)象的引用計(jì)數(shù)或者說(shuō)弱引用存儲(chǔ)都放在一張大表當(dāng)中,這個(gè)時(shí)候如果我們要操作某一個(gè)對(duì)象的引用計(jì)數(shù)值進(jìn)行修改,比如說(shuō)進(jìn)行加1或減1的操作的話,由于所有的對(duì)象可能是在不同的線程當(dāng)中去分配創(chuàng)建的,包括調(diào)用他們的release,retain等方法,也可能是在不同的線程當(dāng)中進(jìn)行操作;這個(gè)時(shí)候?qū)σ环N表進(jìn)行操作的時(shí)候,需要進(jìn)行加鎖處理,才能保證對(duì)于數(shù)據(jù)的訪問(wèn)安全,在這個(gè)過(guò)程中就存在了一個(gè)效率問(wèn)題。比如說(shuō)用戶的內(nèi)存空間一共有4GB,那么可能分配出成千上百萬(wàn)個(gè)內(nèi)存對(duì)象,如果說(shuō)每一個(gè)對(duì)象在對(duì)他進(jìn)行內(nèi)存引用計(jì)數(shù)的改變的時(shí)候,都操作這張表很顯然就會(huì)有效率的問(wèn)題。如果說(shuō)已經(jīng)又一個(gè)對(duì)象在操作這張表,下一個(gè)對(duì)象就要等他操作完,把鎖釋放之后再進(jìn)行操作,這效率就會(huì)太低了。

one_side_table04.png

系統(tǒng)為了解決效率問(wèn)題,引入了分離鎖的技術(shù)方案。我們可以把內(nèi)存對(duì)象所對(duì)應(yīng)的引用計(jì)數(shù)表,可以分拆成多個(gè)部分。比如說(shuō)分拆成8個(gè),需要對(duì)8個(gè)表分別加鎖。當(dāng)A和B同時(shí)進(jìn)行引用計(jì)數(shù)操作的話可以進(jìn)行并發(fā)操作,如果是一張表他們需要進(jìn)行順序操作。很明顯分離鎖可以提高訪問(wèn)效率。

怎樣實(shí)現(xiàn)快速分流?

快速分流指通過(guò)一個(gè)對(duì)象的指針如何快速定位到它屬于那張side table 表當(dāng)中。

hash表.png

side tables的本質(zhì)是一張Hash表。這張hash表當(dāng)中,可能有64張具體的side table 存儲(chǔ)不同對(duì)象的引用計(jì)數(shù)表和弱引用表。

自旋鎖 Spinlock_t

  • Spinlock_t是"忙等"的鎖。

    如果當(dāng)前鎖已被其他線程獲取,那么當(dāng)前線程會(huì)不斷的探測(cè)這個(gè)鎖是否有被釋放,如果釋放掉,自己第一時(shí)間去獲取這個(gè)鎖。所以說(shuō)自旋鎖是一種忙等的鎖。獲取不到鎖的時(shí)候,他會(huì)他自己的線程阻塞休眠,然后等到其他線程釋放這個(gè)鎖的時(shí)候來(lái)喚醒當(dāng)前線程。

  • 適用于輕量訪問(wèn)。

引用計(jì)數(shù)表RefcountMap

引用計(jì)數(shù)表實(shí)際上是一個(gè)hash表,我們可以通過(guò)指針來(lái)找到對(duì)應(yīng)對(duì)象的引用天計(jì)數(shù),這一過(guò)程實(shí)際上也是hash查找(使用hash查找是為了提高查找效率)。

refcount_map.png

插入和獲取是通過(guò)同一個(gè)hash函數(shù)完成,避免了遞歸查找和for循環(huán)遍歷

size_t內(nèi)存分配

size_t.png
  • 第一個(gè)二進(jìn)制位表示的是weakly_referenced,對(duì)象是否有弱引用,0沒(méi)有,1有。
  • 第二位deallocating表示當(dāng)前對(duì)象是否處于dealloc
  • 后面(RC)存儲(chǔ)的是對(duì)象的實(shí)際引用計(jì)數(shù)值,在實(shí)際計(jì)算這個(gè)引用計(jì)數(shù)值,需要向右偏移兩位,因?yàn)楹竺鎯晌恍枰サ簟?/li>

弱引用表weak_table_t

weak_table_t實(shí)際上也是一個(gè)hash表.

weak_table_t.png

weak_entry_t實(shí)際上是一個(gè)結(jié)構(gòu)體數(shù)組。結(jié)構(gòu)體數(shù)組存儲(chǔ)的是每一個(gè)的弱引用指針,也就是代碼當(dāng)中定義的__weak id obj,obj內(nèi)存地址即指針就存儲(chǔ)在weak_entry_t

MRC & ARC

MRC 手動(dòng)引用計(jì)數(shù)

  • alloc: 用來(lái)分配一個(gè)對(duì)象的內(nèi)存空間。
  • retain:對(duì)一個(gè)對(duì)象的引用計(jì)數(shù)加1;
  • release:對(duì)一個(gè)對(duì)象的引用計(jì)數(shù)減1;
  • retainCount:獲取當(dāng)前對(duì)象的引用計(jì)數(shù)值
  • autorelease:如果調(diào)用了一個(gè)對(duì)象的autorelease方法,當(dāng)前這個(gè)對(duì)象會(huì)在autoreleasepool結(jié)束的時(shí)候,調(diào)用他的release操作進(jìn)行引用計(jì)數(shù)減1.
  • dealloc:在MRC當(dāng)中調(diào)用dealloc方法需要顯式調(diào)用[super dealloc]來(lái)釋放或廢棄父類的相關(guān)成員變量。

ARC 自動(dòng)引用計(jì)數(shù)

  • ARC是LLVM和Runtime協(xié)作來(lái)進(jìn)行自動(dòng)引用計(jì)數(shù)管理;
  • ARC中禁止手動(dòng)調(diào)用retain,release,retainCount,dealloc,并且在ARC中可以重寫某個(gè)對(duì)象的dealloc方法,但是不能再dealloc方法當(dāng)中,顯示調(diào)用[super dealloc];
  • ARC中新增了weak,strong屬性關(guān)鍵字。

ARC實(shí)際是由編譯期自動(dòng)為我們插入retainrelease操作之外,還需要runtime的功能進(jìn)行支持,然后由編譯器和Runtime共同協(xié)作才能組成ARC的全部功能。

引用計(jì)數(shù)管理

實(shí)現(xiàn)原理分析

  • alloc
  • retain
  • release
  • retainCount
  • dealloc

alloc實(shí)現(xiàn)

  • 經(jīng)過(guò)一系列調(diào)用,最終調(diào)用了C函數(shù)calloc
  • 此時(shí)并沒(méi)有設(shè)置引用計(jì)數(shù)為1

retain實(shí)現(xiàn)

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
  //hash查找SideTable
    SideTable& table = SideTables()[this];
    
  //SideTable加鎖
    table.lock();
  //hash查找引用計(jì)數(shù)值
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
      //引用計(jì)數(shù)加1
      //#define SIDE_TABLE_RC_ONE            (1UL<<2)
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
  1. 通過(guò)當(dāng)前對(duì)象的指針this,經(jīng)過(guò)hash函數(shù)的計(jì)算,可以快速的SideTables當(dāng)中找到(hash查找)它對(duì)應(yīng)的SideTable
  2. 然后在SideTable當(dāng)中獲取應(yīng)用計(jì)數(shù)map這個(gè)成員變量,通過(guò)對(duì)象的指針this,在SideTable的引用計(jì)數(shù)表中獲取(hash查找)當(dāng)前當(dāng)前對(duì)象的引用計(jì)數(shù)值。
  3. 經(jīng)過(guò)一定的條件判斷之后,引用計(jì)數(shù)加1。

引用計(jì)數(shù)加1,實(shí)際是加上了偏移量對(duì)應(yīng)的操作,這個(gè)偏移量是4,反應(yīng)出來(lái)的結(jié)果是加1,因?yàn)?code>size_t64位,前兩位不是存儲(chǔ)引用計(jì)數(shù),所以需要向左偏移兩位操作1UL<<2

release實(shí)現(xiàn)

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
  //hash查找SideTable
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

  //加鎖
    table.lock();
  //根據(jù)當(dāng)前對(duì)象指針,訪問(wèn)table的應(yīng)用計(jì)數(shù)表
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
      //引用計(jì)數(shù)減1操作
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}
  1. 通過(guò)當(dāng)前對(duì)象的指針this,經(jīng)過(guò)hash函數(shù)的計(jì)算,可以快速的SideTables當(dāng)中找到(hash查找)它對(duì)應(yīng)的SideTable。
  2. 根據(jù)當(dāng)前對(duì)象指針,訪問(wèn)table的應(yīng)用計(jì)數(shù)表
  3. 找到對(duì)應(yīng)的值進(jìn)行引用計(jì)數(shù)減1操作

retainCount實(shí)現(xiàn)

uintptr_t
objc_object::sidetable_retainCount()
{
  //hash查找SideTable
    SideTable& table = SideTables()[this];
  //聲明局部變量賦值為1
    size_t refcnt_result = 1;
    
  //加鎖
    table.lock();
  //根據(jù)當(dāng)前對(duì)象指針,訪問(wèn)table的應(yīng)用計(jì)數(shù)表
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
      //查找結(jié)果向右位移2位,再加上局部變量的值
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}
  1. 通過(guò)當(dāng)前對(duì)象的指針this,經(jīng)過(guò)hash函數(shù)的計(jì)算,可以快速的SideTables當(dāng)中找到(hash查找)它對(duì)應(yīng)的SideTable。

  2. 根據(jù)當(dāng)前對(duì)象指針,訪問(wèn)table的應(yīng)用計(jì)數(shù)表

  3. 找到對(duì)應(yīng)的值向右位移2位,再加上局部變量的值1

    這就是alloc操作之后,引用計(jì)數(shù)沒(méi)有變化,但retainCount獲取的值是1的原因

dealloc實(shí)現(xiàn)源碼

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

dealloc實(shí)現(xiàn)流程圖
dealloc.png
object_dispose()實(shí)現(xiàn)
object_dispose()實(shí)現(xiàn).png
objc_destructInstance()實(shí)現(xiàn)
objc_destructInstance()實(shí)現(xiàn).png
clearDeallocating()實(shí)現(xiàn)
clearDeallocating實(shí)現(xiàn).png

弱引用管理

弱引用管理.png
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

添加weak變量的弱引用實(shí)現(xiàn)

weak調(diào)用棧.png

當(dāng)一個(gè)對(duì)象被釋放或者廢棄之后,weak變量怎樣處理的?

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
   
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        //referrers取到弱引用指針的所有對(duì)應(yīng)的數(shù)組列表
        objc_object **referrer = referrers[I];
        if (referrer) {//如果referrer即弱引用指針存在
            if (*referrer == referent) { //如果弱引用指針對(duì)應(yīng)的是被廢棄的對(duì)象的話,就將指針置為nil
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

當(dāng)一個(gè)對(duì)象被dealloc之后,內(nèi)部實(shí)現(xiàn)當(dāng)中會(huì)去調(diào)用weak_clear_no_lock()函數(shù),函數(shù)實(shí)現(xiàn)內(nèi)部會(huì)根據(jù)弱引用指針查找弱引用表把當(dāng)前對(duì)象相對(duì)應(yīng)的弱引用拿出來(lái),然后遍歷數(shù)組的所有弱引用指針,分別置為nil

自動(dòng)釋放池

編譯期會(huì)將代碼塊@autoreleasepool{}改寫為:

  1. void *ctx = objc_autoreleasePoolPush();

  2. {}中的代碼

  3. objc_autoreleasePoolPop(ctx);

    一次pop實(shí)際上相當(dāng)于一次批量的pop操作

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

自動(dòng)釋放池的數(shù)據(jù)結(jié)構(gòu)

  • 是以為節(jié)點(diǎn)通過(guò)雙向鏈表的形式組合而成。(什么是自動(dòng)釋放池/自動(dòng)釋放池的數(shù)據(jù)結(jié)構(gòu)是怎樣的?)
  • 是和線程一一對(duì)應(yīng)的

雙向鏈表

雙向鏈表.png

棧結(jié)構(gòu).png

AutoreleasePoolPage類源碼

class AutoreleasePoolPage 
{
    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;//指向當(dāng)前棧中下一個(gè)可填充的位置
    pthread_t const thread; //線程
    AutoreleasePoolPage * const parent;//雙向鏈表的父指針
    AutoreleasePoolPage *child;//雙向鏈表的子指針
    uint32_t const depth;
    uint32_t hiwat;

    // SIZE-sizeof(*this) bytes of contents follow

    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    static void operator delete(void * p) {
        return free(p);
    }
  
  .....
}

AutoreleasePoolPage結(jié)構(gòu)

autoreleasePoolPage.png

AutoreleasePoolPage::push

autoreleasePoolPage_push.png

[obj autorelease]

obj_autorelease.png

AutoreleasePoolPage::pop

  • 根據(jù)傳入的哨兵對(duì)象找到對(duì)應(yīng)位置。
  • 給上次push操作之后添加的對(duì)象依次發(fā)送release消息。
  • 回退next指針到正確位置。
autoreleasePoolPage_pop01.png

autoreleasePoolPage_pop02.png

總結(jié)

  • 當(dāng)每次runloop將要結(jié)束的時(shí)候調(diào)用AutoreleasePoolPage::pop()
  • 多層嵌套就是多次插入哨兵對(duì)象。
  • 在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場(chǎng)景手動(dòng)插入autoreleasePool
- (void)viewDidLoad
{
  [super viewDidLoad];
  NSMutableArray *array = [NSMutableArray array];
  NSLog(@"%@",array);
}

上面array的內(nèi)存在什么時(shí)候釋放的?

當(dāng)每次runloop將要結(jié)束的時(shí)候,都會(huì)對(duì)前一次創(chuàng)建的AutoreleasePool調(diào)用AutoreleasePoolPage::pop()操作,同時(shí)會(huì)push進(jìn)來(lái)一個(gè)AutoreleasePool。所以array對(duì)象會(huì)在當(dāng)前runloop就要結(jié)束的時(shí)候調(diào)用AutoreleasePoolPage::pop()方法,把對(duì)應(yīng)的array對(duì)象,調(diào)用其release函數(shù)對(duì)其進(jìn)行釋放

AutoreleasePool的實(shí)現(xiàn)原理是怎樣的?

是以為節(jié)點(diǎn)通過(guò)雙向鏈表的形式組合而成的數(shù)據(jù)結(jié)構(gòu)

AutoreleasePool為何可以嵌套使用?

多層嵌套就是多次插入哨兵對(duì)象。在我們每次創(chuàng)建代碼塊@autoreleasepool{},系統(tǒng)就會(huì)為我們進(jìn)行哨兵對(duì)象的插入,完成新的AutoreleasePool的創(chuàng)建,實(shí)際上也是創(chuàng)建了一個(gè)AutoreleasePoolPage,假如當(dāng)前AutoreleasePoolPage沒(méi)有滿的話,就不用創(chuàng)建AutoreleasePoolPage。所以新創(chuàng)建的AutoreleasePool底層就是插入一個(gè)哨兵對(duì)象,所以可以多層嵌套。

循環(huán)引用

三種循環(huán)引用

  • 自循環(huán)引用
  • 相互循環(huán)引用
  • 多循環(huán)引用

自循環(huán)引用

自循環(huán)引用.png

相互循環(huán)引用

相互循環(huán)引用.png

多循環(huán)引用

多循環(huán)引用.png

循環(huán)引用考點(diǎn)

  • 代理
  • Block
  • NSTimer
  • 大環(huán)引用

如何破除循環(huán)引用?

  • 避免產(chǎn)生循環(huán)引用
  • 在合適的時(shí)機(jī)手動(dòng)斷環(huán)

破除循環(huán)引用具體的解決方案都有哪些?

  • __weak破解
  • __block破解
  • __unsafe_unretained破解

__weak破解

weak破解.png

__block破解

  • MRC下,__block修飾對(duì)象不會(huì)增加其引用計(jì)數(shù),避免了循環(huán)引用。
  • ARC下,__block修飾對(duì)象會(huì)被強(qiáng)引用,無(wú)法避免循環(huán)引用,需手動(dòng)解環(huán)。

__unsafe_unretained破解

  • 修飾對(duì)象不會(huì)增加其引用計(jì)數(shù),避免了循環(huán)引用。
  • 如果被修飾對(duì)象在某一時(shí)機(jī)被釋放,會(huì)產(chǎn)生懸垂指針!

循環(huán)引用示例

  • Block的使用示例。(參看Block的講解)

  • NSTimer使用示例。

nstimer方案.png
#import "NSTimer+WeakTimer.h"

@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;

- (void)fire:(NSTimer *)timer;
@end

@implementation TimerWeakObject

- (void)fire:(NSTimer *)timer
{
    if (self.target) {
        if ([self.target respondsToSelector:self.selector]) {
            [self.target performSelector:self.selector withObject:timer.userInfo];
        }
    }
    else{
        [self.timer invalidate];
    }
}

@end

@implementation NSTimer (WeakTimer)

+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats
{
    TimerWeakObject *object = [[TimerWeakObject alloc] init];
    object.target = aTarget;
    object.selector = aSelector;
    object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    
    return object.timer;
}

@end

內(nèi)存管理面試總結(jié)

  • 什么是ARC?
  • 為什么weak指針指向的對(duì)象在廢棄之后會(huì)被自動(dòng)置為nil?
  • 蘋果是如何實(shí)現(xiàn)AutoreleasePool的?
  • 什么是循環(huán)引用?你遇到過(guò)哪些循環(huán)引用,是怎樣解決的?
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、內(nèi)存布局 stack:方法調(diào)用 heap:通過(guò)alloc等分配對(duì)象 bss:未初始化的全局變量等。 data:...
    AKyS佐毅閱讀 1,712評(píng)論 0 19
  • 文章目錄 一.內(nèi)存管理準(zhǔn)則 二.屬性內(nèi)存管理修飾符全解析 三.block中的weak和strong 四.weak是...
    YouKnowZrx閱讀 1,117評(píng)論 5 10
  • 前言 從我開(kāi)始學(xué)習(xí)iOS的時(shí)候,身邊的朋友、網(wǎng)上的博客都告訴我iOS的內(nèi)存管理是依靠引用計(jì)數(shù)的,然后說(shuō)引用計(jì)數(shù)大于...
    蓋世英雄_ix4n04閱讀 657評(píng)論 0 1
  • 一.面試問(wèn)題 使用CADisplayLink、NSTimer有什么注意點(diǎn)?循環(huán)引用、NSTimer定時(shí)器不準(zhǔn) 介紹...
    蔚尼閱讀 931評(píng)論 0 1
  • 29.理解引用計(jì)數(shù) Objective-C語(yǔ)言使用引用計(jì)數(shù)來(lái)管理內(nèi)存,也就是說(shuō),每個(gè)對(duì)象都有個(gè)可以遞增或遞減的計(jì)數(shù)...
    Code_Ninja閱讀 1,725評(píng)論 1 3

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