iOS 內(nèi)存管理

1 面試:全局變量和局部變量在內(nèi)存中是否有區(qū)別镰踏?如果有,是什么區(qū)別须蜗?

局部變量定義在局部的空間搀缠,全局變量定義在全局的區(qū)域蓉坎,存儲(chǔ)的區(qū)域不一樣

2 block 是否可以直接修改全局變量?

可以胡嘿,全局變量作用空間特別大,是公共的

3靜態(tài)區(qū)安全測(cè)試

全局靜態(tài)變量可以修改 只對(duì)文件有效钳踊,不是對(duì)類有效
新建一個(gè)LGPerson的類

//.h文件
#import <Foundation/Foundation.h>

static int personNum = 100;

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject

- (void)run;
+ (void)eat;
@end


//.m文件
@implementation LGPerson
- (void)run{
    personNum ++;
    NSLog(@"LGPerson內(nèi)部:%@-%p--%d",self,&personNum,personNum);
}

+ (void)eat{
    personNum ++;
    NSLog(@"LGPerson內(nèi)部:%@-%p--%d",self,&personNum,personNum);
}

- (NSString *)description{
    return @"";
}

@end

再新建一個(gè)LGPerson的一個(gè)分類

#import "LGPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson (LG)
- (void)cate_method;
@end


//.m文件
@implementation LGPerson (LG)
- (void)cate_method{
    NSLog(@"LGPerson內(nèi)部:%@-%p--%d",self,&personNum,personNum);
}

@end

調(diào)用

// 100 可以修改
    // 只針對(duì)文件有效 -
    NSLog(@"vc:%p--%d",&personNum,personNum); // 100
    personNum = 10000;
    NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
    [[LGPerson new] run]; // 100 + 1 = 101
    NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
    [LGPerson eat]; // 102
    NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
   
    [[LGPerson alloc] cate_method];

打印結(jié)果為

2020-11-07 12:10:39.026774+0800 001---五大區(qū)Demo[5285:436872] vc:0x101bc23cc--100
2020-11-07 12:10:39.027201+0800 001---五大區(qū)Demo[5285:436872] vc:0x101bc23cc--10000
2020-11-07 12:10:39.027532+0800 001---五大區(qū)Demo[5285:436872] LGPerson內(nèi)部:-0x101bc23b0--101
2020-11-07 12:10:39.027790+0800 001---五大區(qū)Demo[5285:436872] vc:0x101bc23cc--10000
2020-11-07 12:10:39.028165+0800 001---五大區(qū)Demo[5285:436872] LGPerson內(nèi)部:LGPerson-0x101bc23b0--102
2020-11-07 12:10:39.028627+0800 001---五大區(qū)Demo[5285:436872] vc:0x101bc23cc--10000
2020-11-07 12:10:39.029702+0800 001---五大區(qū)Demo[5285:436872] LGPerson內(nèi)部:-0x101bc23d0--100

Tagged Pointer

先看一個(gè)面試題

- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci"];
             NSLog(@"%@",self.nameStr);
        });
    }
}



- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    // 多線程
    // setter getter
    /**
     retian newvalue
     realase oldvalue
     taggedpointer 影響
     */
    
    NSLog(@"來(lái)了");
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci_和諧學(xué)習(xí)不急不躁"];
            NSLog(@"%@",self.nameStr);
        });
    }
}

運(yùn)行taggedPointerDemo衷敌,代碼運(yùn)行沒(méi)問(wèn)題,點(diǎn)擊運(yùn)行另一塊代碼拓瞪,項(xiàng)目崩潰缴罗,之前多線程部分講到新值替換舊值的一瞬間可能會(huì)為空,導(dǎo)致程序崩潰祭埂,那么為什么上面的沒(méi)有問(wèn)題面氓,下面的崩潰呢?添加斷點(diǎn)蛆橡,打印得知:
當(dāng)self.nameStr為cooci時(shí)舌界,類型為NSTaggedPointerString


屏幕快照 2020-11-07 下午10.46.08.png

當(dāng)self.nameStr為cooci_和諧學(xué)習(xí)不急不躁時(shí),類型為NSCFString泰演,

屏幕快照 2020-11-07 下午10.50.42.png

打開(kāi)源碼呻拌,搜索release方法

__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}


__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

可以發(fā)現(xiàn)不會(huì)對(duì)TaggedPointer類型進(jìn)行release,retain操作睦焕,一般是在8到10位左右長(zhǎng)度的小對(duì)象藐握,編譯讀取的時(shí)候更加直接,釋放是由系統(tǒng)直接回收

總結(jié):

1:Tagged Pointer專?用來(lái)存儲(chǔ)小的對(duì)象垃喊,例如NSNumber和NSDate
2:Tagged Pointer指針的值不再是地址了猾普,而是真正的值。所以本谜,實(shí)際上它不再 是一個(gè)對(duì)象了初家,它只是一個(gè)披著對(duì)象皮的普通變量而已。所以耕突,它的內(nèi)存并不存儲(chǔ) 在堆中笤成,也不需要malloc和free
3.在內(nèi)存讀取上有著3倍的效率,創(chuàng)建時(shí)比以前快106倍眷茁。

NONPOINTER_ISA

NONPOINTER_ISA同樣是蘋(píng)果公司對(duì)于內(nèi)存優(yōu)化的一種方案炕泳。用 64 bit 存儲(chǔ)一個(gè)內(nèi)存地址顯然是種浪費(fèi),畢竟很少有那么大內(nèi)存的設(shè)備上祈。于是可以優(yōu)化存儲(chǔ)方案培遵,用一部分額外空間存儲(chǔ)其他內(nèi)容浙芙。isa 指針第一位為 1 即表示使用優(yōu)化的 isa 指針,這里列出在x86_64架構(gòu)下的 64 位環(huán)境中 isa 指針結(jié)構(gòu)籽腕,arm64的架構(gòu)會(huì)有所差別嗡呼。

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
      uintptr_t nonpointer        : 1;                                         
      uintptr_t has_assoc         : 1;                                         
      uintptr_t has_cxx_dtor      : 1;                                         
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ 
      uintptr_t magic             : 6;                                         
      uintptr_t weakly_referenced : 1;                                         
      uintptr_t deallocating      : 1;                                         
      uintptr_t has_sidetable_rc  : 1;                                         
      uintptr_t extra_rc          : 8
    };
#endif
};


nonpointer:示是否對(duì)isa開(kāi)啟指針優(yōu)化。0代表是純isa指針皇耗,1代表除了地址外南窗,還包含了類的一些信息、對(duì)象的引用計(jì)數(shù)等
has_assoc:關(guān)聯(lián)對(duì)象標(biāo)志位郎楼,0沒(méi)有万伤,1存在。
has_cxx_dtor:該對(duì)象是否有C++或Objc的析構(gòu)器呜袁,如果有析構(gòu)函數(shù)敌买,則需要做一些析構(gòu)的邏輯處理,如果沒(méi)有阶界,則可以更快的釋放對(duì)象虹钮。
shiftcls:存在類指針的值,開(kāi)啟指針優(yōu)化的情況下膘融,arm64位中有33位來(lái)存儲(chǔ)類的指針
magic:判斷當(dāng)前對(duì)象是真的對(duì)象還是一段沒(méi)有初始化的空間
weakly_referenced:是否被指向或者曾經(jīng)指向一個(gè)ARC的弱變量芙粱,沒(méi)有弱引用的對(duì)象釋放的更快。
deallocating:標(biāo)志是否正在釋放內(nèi)存氧映。
has_sidetable_rc:是否有輔助的引用計(jì)數(shù)散列表宅倒。當(dāng)對(duì)象引?技術(shù)?于 10 時(shí),則需要借?該變量存儲(chǔ)進(jìn)位屯耸。
extra_rc:表示該對(duì)象的引?計(jì)數(shù)值拐迁,實(shí)際上是引?計(jì)數(shù)值減 1,例如疗绣,如果對(duì)象的引?計(jì)數(shù)為 10线召,那么 extra_rc 為 9。如果引?計(jì)數(shù)?于 10多矮,則需要使?到下?的 has_sidetable_rc缓淹。

內(nèi)存管理

retain是如何處理的

查看源碼
retain方法會(huì)調(diào)用方法rootRetain,

objc_object::retain()
{
    ASSERT(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}

繼續(xù)查看方法rootRetain

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    // retain 引用計(jì)數(shù)處理
    // 
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // 散列表的引用計(jì)數(shù)表 進(jìn)行處理 ++
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

首先判斷是不是nonpointer isa ,如果不是塔逃,引用計(jì)數(shù)存儲(chǔ)在散列表里面
這個(gè)散列表包含哪些東西呢讯壶?我們進(jìn)源碼看一下:

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

可以看到散列表包含一把鎖 lock,引用計(jì)數(shù)表湾盗,弱引用表伏蚊。
在代碼中我們看到有SideTables,可以知道里面有多張散列表格粪,出于安全和性能問(wèn)題躏吊,里面有多張表氛改。數(shù)量不得知,大概64張比伏。
通過(guò)哈希結(jié)構(gòu)存儲(chǔ)多張散列表胜卤。

retain是對(duì)引用計(jì)數(shù)進(jìn)行處理,也是對(duì)散列表的引用計(jì)數(shù)表進(jìn)行處理 ++ --赁项。
如果是nonpointer isa葛躏,調(diào)用addc,對(duì)bits里面的位數(shù)進(jìn)行操作

newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);

這個(gè)地方如果超負(fù)荷的話悠菜,就會(huì)對(duì)散列表進(jìn)行操作紫新,會(huì)把原來(lái)的一半存儲(chǔ)在extra_rc里面,另一半存儲(chǔ)在散列表里面

if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }

這里優(yōu)先isa李剖,是因?yàn)閑xtra_rc比散列表的操作要快

release操作

我們查看源碼,發(fā)現(xiàn)release方法會(huì)調(diào)用rootRelease方法囤耳。

objc_object::release()
{
    ASSERT(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        rootRelease();
        return;
    }

    ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}

繼續(xù)查看rootRelease方法篙顺,其原理與retain一致,這里需要注意一點(diǎn)充择,就是dealloc方法是在哪里調(diào)用的呢德玫?
是在release調(diào)用的時(shí)候,當(dāng)引用計(jì)數(shù)為0的時(shí)候椎麦,會(huì)通過(guò)objc_msgSend方式調(diào)用

objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.        
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

retainCount

先看一個(gè)面試題:

        NSObject *objc = [NSObject alloc]; // 0
        NSLog(@"%ld",objc.retainCount); // 1
        NSLog(@"%ld",objc.retainCount); // 1

兩次打印結(jié)果都為1宰僧,那么這里請(qǐng)問(wèn)alloc出來(lái)的對(duì)象引用計(jì)數(shù)為1 是否正確?答案錯(cuò)誤观挎,alloc出來(lái)的對(duì)象引用計(jì)數(shù)為0.

因?yàn)閍lloc 源碼里沒(méi)有對(duì)引用計(jì)數(shù)進(jìn)行操作琴儿,那我們看下retainCount的源碼,bits.extra_rc為0,可以發(fā)現(xiàn)alloc出來(lái)的對(duì)象引用計(jì)數(shù)為0嘁捷,打印出來(lái)為1是因?yàn)檫@里會(huì)默認(rèn)給他一個(gè)1造成。再次打印一次retainCount還是為1,是因?yàn)檫@里的bits.extra_rc并沒(méi)有存值雄嚣,還是為0晒屎,所以打印出來(lái)的還是1。

inline uintptr_t 
objc_object::rootRetainCount() // 1
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        // bits.extra_rc = 0;
        // 
        uintptr_t rc = 1 + bits.extra_rc; // isa
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock(); // 散列表
        }
        sidetable_unlock();
        return rc; // 1
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

dealloc

下面我們分析dealloc操作源碼缓升,這里對(duì)調(diào)用方法rootDealloc鼓鲁,然后是object_dispose方法

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

object_dispose方法

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    // weak
    // cxx
    // 關(guān)聯(lián)對(duì)象
    // ISA 64
    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

在free方法之前走方法objc_destructInstance

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

然后走方法clearDeallocating,在這里進(jìn)行散列表清空港谊,如果含有弱引用計(jì)數(shù)骇吭,調(diào)用方法clearDeallocating_slow,繼續(xù)清理引用計(jì)數(shù)表

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());
}
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) { // 清理引用計(jì)數(shù)表
        table.refcnts.erase(this);
    }
    table.unlock();
}

總結(jié)一下上面的流程可以如下所示:


LG_Cooci_dealloc.png

強(qiáng)引用

我們知道打破block強(qiáng)引用的話歧寺,可以用weakSelf解決绵跷,self指向block膘螟,block指向weakSelf,來(lái)打破循環(huán)引用碾局,注意一點(diǎn)荆残,這個(gè)block捕獲的是weakSelf這個(gè)臨時(shí)變量的指針地址,不是這個(gè)對(duì)象净当,weakSelf和self是兩個(gè)不同的指針地址内斯,指向了同一個(gè)對(duì)象,所以打破了這里的循環(huán)引用像啼。
self -> block -> weakSelf(臨時(shí)變量的指針地址)

下面我們?cè)倏匆粋€(gè)例子俘闯,這里會(huì)形成一個(gè)強(qiáng)引用,self持有timer忽冻,timer持有self

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

如果我們也像上面那樣操作用weakSelf是否能解決呢真朗?改成下面這樣

 __weak typeof(self) weakSelf = self; // weak
     self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];

答案是不能,因?yàn)檫@里timer強(qiáng)持有的事weakSelf指針指向的對(duì)象僧诚,也就是self指針指向的對(duì)象遮婶。

自動(dòng)釋放池

自動(dòng)釋放池始于MRC時(shí)代,主要是用于 自動(dòng) 對(duì) 釋放池內(nèi) 對(duì)象 進(jìn)行引用計(jì)數(shù)-1的操作湖笨,即自動(dòng)執(zhí)行release方法,在MRC中使用autoreleasepool必須在代碼塊內(nèi)部手動(dòng)為對(duì)象調(diào)用autorelease把對(duì)象加入到的自動(dòng)釋放池旗扑,系統(tǒng)會(huì)自動(dòng)在代碼塊結(jié)束后,對(duì)加入自動(dòng)釋放池中的對(duì)象發(fā)送一個(gè)release消息.無(wú)需手動(dòng)調(diào)用release.

通過(guò)clang生成cpp文件慈省,查看

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      }
}

在cpp文件顯示為這個(gè)結(jié)構(gòu)體的調(diào)用臀防,也就是用走方法__AtAutoreleasePool和~__AtAutoreleasePool()

 struct __AtAutoreleasePool {
   __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
   ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
   void * atautoreleasepoolobj;
 };

先看方法objc_autoreleasePoolPush,搜索源碼

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

繼續(xù)往下點(diǎn)擊

屏幕快照 2020-11-09 下午11.20.27.png

這里看到繼承自AutoreleasePoolPageData边败,我們看下AutoreleasePoolPageData是什么

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic; // 16
    __unsafe_unretained id *next; //8
    pthread_t const thread; // 8
    AutoreleasePoolPage * const parent; //8
    AutoreleasePoolPage *child; //8
    uint32_t const depth; // 4
    uint32_t hiwat; // 4

    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};
struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1; // 靜態(tài)變量不在這里存儲(chǔ)
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4];    //4*4 = 16

    magic_t() {
        ASSERT(M1_len == strlen(M1));
        ASSERT(M1_len == 3 * sizeof(m[1]));

        m[0] = M0;
        strncpy((char *)&m[1], M1, M1_len);
    }

    ~magic_t() {
        // Clear magic before deallocation.
        // This prevents some false positives in memory debugging tools.
        // fixme semantically this should be memset_s(), but the
        // compiler doesn't optimize that at all (rdar://44856676).
        volatile uint64_t *p = (volatile uint64_t *)m;
        p[0] = 0; p[1] = 0;
    }

AutoreleasePoolPageData是個(gè)結(jié)構(gòu)體,里面有自己的一些成員變量袱衷,里面的字段的意義如下

magic 用來(lái)校驗(yàn) AutoreleasePoolPage 的結(jié)構(gòu)是否完整;
? next 指向最新添加的 autoreleased 對(duì)象的下一個(gè)位置,初始化時(shí)指向
begin() ;
? thread 指向當(dāng)前線程;
? parent 指向父結(jié)點(diǎn)笑窜,第一個(gè)結(jié)點(diǎn)的 parent 值為 nil ;
? child 指向子結(jié)點(diǎn)祟昭,最后一個(gè)結(jié)點(diǎn)的 child 值為 nil ;
? depth 代表深度,從 0 開(kāi)始怖侦,往后遞增 1;
? hiwat 代表 high water mark 最大入棧數(shù)量標(biāo)記

總結(jié)一下:

AutoreleasePoolPage其實(shí)就是一個(gè)雙向鏈表結(jié)構(gòu),AutoreleasePoolPage(自動(dòng)釋放池頁(yè)) 用來(lái)存放 autorelease 的對(duì)象篡悟,但是每一頁(yè)的大小是有限制的,假如某個(gè)AutoreleasePoolPage頁(yè)中需要存放的autorelease 的對(duì)象過(guò)多,一頁(yè)存放不完,所以它就需要指向父結(jié)點(diǎn)點(diǎn),在指向父結(jié)點(diǎn)里的AutoreleasePoolPage頁(yè)中繼續(xù)存放.
那么每一頁(yè)大小是多少呢?

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;

public:
    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2
.
.
.
#endif
}

有個(gè)PAGE_MAX_SIZE,點(diǎn)擊進(jìn)去是4096.原來(lái)每一頁(yè)AutoreleasePoolPage可以存放4096個(gè)字節(jié).一共4096個(gè)字節(jié)匾寝, (4096 - AutoreleasePoolPage 中自己成員變量所占的字節(jié))/每個(gè)對(duì)象中所占的字節(jié). (4096 - 56)/8 = 505. 好的,每一AutoreleasePoolPage可以存放505個(gè)對(duì)象搬葬。


image

一個(gè)autoreleasePoolPage最多能添加504個(gè)8字節(jié)對(duì)象是否正確?

第一頁(yè)是504+1個(gè)對(duì)象艳悔,這一個(gè)不是你添加進(jìn)去的急凰,是一個(gè)標(biāo)記,是邊界,也就是第一頁(yè)最多能添加504個(gè)對(duì)象抡锈,其他頁(yè)最多能添加505個(gè)對(duì)象疾忍。

總結(jié)

在APP中,整個(gè)主線程是運(yùn)行在一個(gè)自動(dòng)釋放池中的床三。

main函數(shù)中的自動(dòng)釋放池的作用:這個(gè)池塊給出了一個(gè)pop點(diǎn)來(lái)顯式的告訴我們這里有一個(gè)釋放點(diǎn)一罩,如果你的main在初始化的過(guò)程中有別的內(nèi)容可以放在這里。

使用@autoreleasepool標(biāo)記撇簿,調(diào)用push()方法聂渊。

沒(méi)有hotpage,調(diào)用()四瘫,設(shè)置EMPTY_POOL_PLACEHOLDER汉嗽。

因?yàn)樵O(shè)置了EMPTY_POOL_PLACEHOLDER,所以會(huì)設(shè)置本頁(yè)為hotpage找蜜,添加邊界標(biāo)記POOL_BOUNDARY饼暑,最后添加obj。

繼續(xù)有對(duì)象調(diào)用autorelease洗做,此時(shí)已經(jīng)有了page弓叛,調(diào)用page->add(obj)。

如果page滿了竭望,調(diào)用autoreleaseFullPage()創(chuàng)建新page,重復(fù)第6點(diǎn)裕菠。

到達(dá)autoreleasePool邊界咬清,調(diào)用pop方法,通常情況下會(huì)釋放掉POOL_BOUNDARY之后的所有對(duì)象

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奴潘,一起剝皮案震驚了整個(gè)濱河市旧烧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌画髓,老刑警劉巖掘剪,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奈虾,居然都是意外死亡夺谁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)肉微,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)匾鸥,“玉大人,你說(shuō)我怎么就攤上這事碉纳∥鸶海” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵劳曹,是天一觀的道長(zhǎng)奴愉。 經(jīng)常有香客問(wèn)我琅摩,道長(zhǎng),這世上最難降的妖魔是什么锭硼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任房资,我火速辦了婚禮,結(jié)果婚禮上账忘,老公的妹妹穿的比我還像新娘志膀。我一直安慰自己,他們只是感情好鳖擒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布溉浙。 她就那樣靜靜地躺著,像睡著了一般蒋荚。 火紅的嫁衣襯著肌膚如雪戳稽。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天期升,我揣著相機(jī)與錄音惊奇,去河邊找鬼。 笑死播赁,一個(gè)胖子當(dāng)著我的面吹牛颂郎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播容为,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乓序,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了坎背?” 一聲冷哼從身側(cè)響起替劈,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎得滤,沒(méi)想到半個(gè)月后陨献,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懂更,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年眨业,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沮协。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坛猪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出皂股,到底是詐尸還是另有隱情墅茉,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站就斤,受9級(jí)特大地震影響悍募,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洋机,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一坠宴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绷旗,春花似錦喜鼓、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至角骤,卻和暖如春隅忿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邦尊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工背桐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蝉揍。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓链峭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親又沾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弊仪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本文中的源代碼來(lái)源:需要下載Runtime的源碼,官方的工程需要經(jīng)過(guò)大量調(diào)試才能使用捍掺。這里有處理好的objc4-7...
    shen888閱讀 843評(píng)論 0 3
  • 一 iOS程序內(nèi)存布局 二 Tagged Pointer內(nèi)存地址優(yōu)化 三 MRC概念講解 四 引用計(jì)數(shù)的存儲(chǔ) 五 ...
    當(dāng)前明月閱讀 962評(píng)論 0 2
  • 學(xué)習(xí)了好久的iOS內(nèi)存管理撼短,一直是斷斷續(xù)續(xù)的再膳,現(xiàn)在有時(shí)間找了個(gè)機(jī)會(huì)總結(jié)了一下挺勿,有時(shí)候時(shí)間久了好多知識(shí)點(diǎn)就會(huì)遺忘,希...
    YYFast閱讀 2,121評(píng)論 1 9
  • 1喂柒、內(nèi)存布局 stack:方法調(diào)用 heap:通過(guò)alloc等分配對(duì)象 bss:未初始化的全局變量等不瓶。 data:...
    AKyS佐毅閱讀 1,596評(píng)論 0 19
  • 1. 對(duì)象與類 1.1 對(duì)象 對(duì)象(Class或id)內(nèi)部只有一個(gè)isa_t聯(lián)合體指針。isa_t聯(lián)合體內(nèi)部只有兩...
    我才是臭吉吉閱讀 720評(píng)論 0 2