iOS底層-31:內(nèi)存管理

蘋果內(nèi)存管理方案主要為MRCARC

TaggedPointer:小對象類型困檩,NSDate虱而、NSNumber等
NonpointerIsa:非指針型isa
散列表:引用計數(shù)表尾膊,弱引用表

TaggedPointer

上述代碼中挺尾,兩個name的類型其實是不一樣的变勇。


nameNSTaggedPointerString肥隆,他是經(jīng)過Xcode優(yōu)化的既荚,存放在常量區(qū)。


這里的nameNSCFString巷屿,存放在堆區(qū)固以。

接下來看一下源碼,看看這兩個類型有什么區(qū)別嘱巾。
我們知道setter方法憨琳,在底層會走reallySetProperty

  • 查看objc_retain
id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

當他是TaggedPointer類型的時候,不走retain直接返回旬昭。

  • 查看objc_release
void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

同樣的篙螟,TaggedPointer類型也不走release。不需要進行內(nèi)存管理问拘,在常量區(qū)遍略,由系統(tǒng)釋放。小對象類型讀取比一般對象快了3倍骤坐,創(chuàng)建快了100倍绪杏。

在read_images中有一個處理TaggedPointer的方法

  • 點擊查看initializeTaggedPointerObfuscator
static void
initializeTaggedPointerObfuscator(void)
{
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation.
        DisableTaggedPointerObfuscation) {
        objc_debug_taggedpointer_obfuscator = 0;
    } else {
        // 在iOS 10.14之后,objc_debug_taggedpointer_obfuscator與上~_OBJC_TAG_MASK纽绍,做了一次處理
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
    }
}
  • 搜索objc_debug_taggedpointer_obfuscator蕾久,可以找到以下代碼


    taggedpointer在編碼和解碼的時候,指針ptr要做一次與objc_debug_taggedpointer_obfuscator的異或操作

  • 驗證


    self.name的原地址與objc_debug_taggedpointer_obfuscator異或得到真正的地址拌夏,而且地址中的61僧著,正是aASCII

這不僅是一個簡單的地址,還包含了值障簿。

  • 查看_objc_isTaggedPointer
#   define _OBJC_TAG_MASK (1UL<<63)

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

在64位中盹愚,取最高位,如果有值就是TaggedPointer對象站故。

  • 查看objc_tag_index_t

    這是TaggedPointerflag類型皆怕,2表示NSString3表示NSNumber4表示NSIndexPath

MRC & ARC

retain

objc_retain開始

id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
  • 查看objc_object::retain()

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

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

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

會走rootRetain方法端逼。

  • 查看rootRetain
ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

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;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //判斷是否為nonpointer isa朗兵,不是nonpointer_isa操作散列表rentain
        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();
        }
        //判斷是否正在析構(gòu),沒有必要操作引用計數(shù)
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;//引用計數(shù)+1
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        //carry 是一個標識位顶滩,代表你的引用計數(shù)位用完了余掖,這時候要借助散列表存儲引用計數(shù)
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            //引用計數(shù)滿了之后,把引用計數(shù)的一半存在extra_rc中
            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)) {
        // 引用計數(shù)的另一半存到散列表中
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
release
  • 查看objc_release
void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

TaggedPointer對象直接返回

  • 查看release()
inline void
objc_object::release()
{
    ASSERT(!isTaggedPointer());

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

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

調(diào)用rootRelease

  • 查看rootRelease
ALWAYS_INLINE bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}

ALWAYS_INLINE bool 
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;
        //非nonpointer_isa直接處理散列表
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
       
        uintptr_t carry;
        //引用計數(shù)減1 這里的carry礁鲁,是標志著isa里的 extra_rc不夠減盐欺,需要去散列表處理
        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;
        }

        // 從散列表中拿出滿值的一半      
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        if (borrowed > 0) {
        
            // 滿值的一半減1 賦值給isa的extra_rc
            newisa.extra_rc = borrowed - 1; 
            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.
        }
    }

    //dealloc函數(shù)
    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

查看retainCount源碼

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

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;//引用計數(shù)加1
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();//加上散列表里的引用計數(shù)
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();//非nonpointer 直接返回散列表里的引用計數(shù)
}

當為nonpointer時,返回的引用計數(shù)額外加1仅醇。
非nonpointer時冗美,返回散列表里的引用計數(shù)。

dealloc

dealloc在前面已經(jīng)分析過了傳送們

散列表

查看SideTable源碼

struct SideTable {
    spinlock_t slock;//操作散列表時要開解鎖
    RefcountMap refcnts;//引用計數(shù)表
    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);
};
  • 查看SideTablesMap


真機里StripeCount為8析二,最多有8張表

NSTimer使用問題

1. NSTimer要加入runLoop才會執(zhí)行

上圖代碼粉洼,timeRun方法并不會執(zhí)行,創(chuàng)建NSTimer時使用timerWith方法叶摄,需要將timer加入到runLoop属韧;或者使用scheduledTimer創(chuàng)建NSTimer

    //timerWithTimeInterval創(chuàng)建的需要加入到NSRunLoop
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeRun) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

    //scheduledTimerWithTimeInterval創(chuàng)建
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeRun) userInfo:nil repeats:YES];

2. weakSelf

  • 打印__weak前后的retainCount

    打印結(jié)果:

    retainCount沒有變化

接著在控制臺上打印


可以看到selfweakSelf指針指向同一塊地址,而他們本身的地址不同蛤吓。

3. NSTimer的強引用

頁面退出后宵喂,controller沒有走dealloc方法,self.timer沒有被釋放

處理方法:

  • didMoveToParentViewController釋放timer
- (void)didMoveToParentViewController:(UIViewController *)parent {
    [self.timer invalidate];
    self.timer = nil;
}
  • 使用NSTimerblock形式
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        static int i = 0;
        i++;
        NSLog(@"%d",i);
    }];

可是這里出現(xiàn)了新的問題会傲,雖然VC釋放了锅棕,但是timer還在打印

下面將探索這兩個問題的由來。

  1. 為什么VC釋放不掉淌山?timer釋放不掉裸燎?
  2. 使用block的形式,為什么VC可以釋放了?

NSTimerfoundation庫中泼疑,未開源德绿。這里我們只能查看官方文檔,command + shift + 0搜索timerWithTimeInterval


timer會對target保持一個強引用王浴,知道timer釋放。

既然是這里的強引用引發(fā)的問題梅猿,那我們使用__weak是不是就能解決問題呢氓辣?

//timerWithTimeInterval創(chuàng)建的需要加入到NSRunLoop
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(timeRun) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

查看結(jié)果:


打印正常,還是沒有走dealloc方法袱蚓。__weak不能解決

[NSRunLoop currentRunLoop]強持有 -> timer

self -> block -> weakSelf
self -> timer -> weakSelf -> self
這兩個模型是不一樣的钞啸,block捕捉的是指針地址,timer捕捉的是內(nèi)存。


這是block的源碼体斩,拿到的是指針地址梭稚。如果傳入的是weakSelf,這里拿到的就是weakSelf的指針地址絮吵,與原本的self已經(jīng)沒有關(guān)系了弧烤。而timer是直接強持有<SecondViewController: 0x7fc048412da0>這片內(nèi)存,故__weak 不起作用蹬敲。

既然原因我們知道了暇昂,那么如何解決這個問題呢?
解決思路:打破這一層強持有
思路一:VCdealloc不能來伴嗡,那么我們手動銷毀timer急波,在合適的地方把timer銷毀了,timer銷毀了瘪校,對VC的強持有就沒有了澄暮,能夠調(diào)用dealloc

上述在didMoveToParentViewController銷毀timer就是這樣的例子。

思路二:中介者模式阱扬。不讓timer強持有self泣懊,給一個中介者

//中介者模式
    self.target = [NSObject alloc];
    class_addMethod([NSObject class], @selector(timeRun), (IMP)timeRun, "v@:");
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(timeRun) userInfo:nil repeats:YES];
    

思路三:虛基類Proxy

- (void)viewDidLoad {
    [super viewDidLoad];
      
    self.proxy = [LYProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(timeRun) userInfo:nil repeats:YES];
    
}

@interface LYProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface LYProxy()
@property (nonatomic, weak) id object;
@end

@implementation LYProxy
+ (instancetype)proxyWithTransformObject:(id)object{
    LYProxy *proxy = [LYProxy alloc];
    proxy.object = object;
    return proxy;
}

// 僅僅添加了weak類型的屬性還不夠,為了保證中間件能夠響應(yīng)外部self的事件价认,需要通過消息轉(zhuǎn)發(fā)機制嗅定,讓實際的響應(yīng)target還是外部self,這一步至關(guān)重要用踩,主要涉及到runtime的消息機制渠退。
// 轉(zhuǎn)移
// 強引用 -> 消息轉(zhuǎn)發(fā)

-(id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}
- (void)dealloc{
   
    NSLog(@"%s",__func__);
}

這樣就不會強引用VC,在VC釋放的時候脐彩,銷毀timer碎乃,即可釋放proxy

4. @autoreleasepool自動釋放池
首先思考這幾個問題:

  1. 臨時變量什么時候釋放惠奸?
  2. 自動釋放池原理梅誓?
  3. 自動釋放池能否嵌套使用?

libobjc源碼中佛南,main方法就有一個autoreleasepool梗掰。


使用clang編譯成.cpp文件

可以看到多了一行__AtAutoreleasePool代碼。

  • 全局搜索__AtAutoreleasePool


    可以看到他是一個結(jié)構(gòu)體嗅回,里面有一個atautoreleasepoolobj變量及穗,還有構(gòu)造方法和析構(gòu)方法。
    這也就意味著绵载,autoreleasepool創(chuàng)建的時候會調(diào)用objc_autoreleasePoolPush()埂陆,析構(gòu)的時候會調(diào)用objc_autoreleasePoolPop

  • 開啟匯編苛白,驗證


  • 按住control + step into


    可以看到源碼在libobjc.A.dylib庫中,接下來去libobjc.A.dylib查看源碼就可以了焚虱。

  • libobjc源碼中搜索Autorelease Pool

  1. 線程的自動釋放池购裙,是一堆指針。指針都指向?qū)⒁会尫诺膶ο缶樵裕蛘呤浅刈拥倪吔纭?/li>
  2. pop的時候躏率,所有對象都會釋放。
  3. 堆棧被分為一個雙向鏈接的頁面列表谍咆。
  4. 線程本地存儲禾锤,也可以保存自動釋放對象。

下面開始驗證環(huán)節(jié):

  • 搜索objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
  • 點擊進入AutoreleasePoolPage


    AutoreleasePoolPage是一個類摹察,繼承于AutoreleasePoolPageData

  • 查看AutoreleasePoolPageData


    其中有一個parentchild恩掷,這也就驗證了這是一個雙向鏈表。
    magic:用來校驗AutoreleasePoolPage的結(jié)構(gòu)是否完整
    next:指向最新添加的autoreleased對象的下一個位置供嚎,初始化時指向begin()
    thread:指向當前線程
    parent:父節(jié)點黄娘,第一個節(jié)點的parent值為nil
    child:子節(jié)點,最后一個節(jié)點的child值為nil
    depth:深度克滴,從0開始逼争,往后遞增1
    hiwat:代表high water mark 最大入棧數(shù)量標記

 id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

當前地址 + 自身屬性的內(nèi)存大小

  • 返回查看push()
static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

autoreleaseNewPageautoreleaseFast其實差不多,都是取到hotpage劝赔,把POOL_BOUNDARY哨兵加進去誓焦。

到這里,AutoreleasePoolPage準備工作就做完了着帽,下面看一下如何把對象加入其中杂伟。

  • 借助_objc_autoreleasePoolPrint打印釋放池情況

關(guān)閉ARC,排除ARC的影響

打印結(jié)果如下


上面的兩個是哨兵對象仍翰,由于我這里是嵌套了@autoreleasepool赫粥,所以有兩個哨兵對象。下面五個是NSObject對象予借。

下面我們看一下page的上限在哪越平?把循環(huán)次數(shù)改為1000
打印結(jié)果如下:

image.png

繼續(xù)往下翻,可以找到這樣的結(jié)構(gòu)灵迫。


這里重新生成了一張hot page秦叛,地址后面從0開始。
這里可以計算一下瀑粥,一張表可以存儲505個對象挣跋,這一頁的大小是4096字節(jié),也就是4kb


這里還需要注意一點利凑,在新的一頁中浆劲,并沒有新增哨兵對象。

  • 查看AutoreleasePoolPage結(jié)構(gòu)體

    sizei386中確實為4096字節(jié)

接下來查看- autorelease方法哀澈,

  • 匯編查看


  • 下符號斷點objc_autorelease

  • 運行


    可以看出確實是調(diào)用了objc_autorelease方法

  • 搜索objc_autorelease(

id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

TaggedPointer對象直接return

  • 查看autorelease

這里走的是rootAutorelease方法

  • 查看rootAutorelease

rootAutorelease2方法

  • 查看rootAutorelease2
__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

實際上走的是AutoreleasePoolPageautorelease()方法

  • 查看autorelease(id obj)
 static inline id autorelease(id obj)
    {
        ASSERT(obj);
        ASSERT(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

這里就到了autoreleaseFast方法牌借,與上面加入哨兵是同一個方法。

  • 查看autoreleaseFast
 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }
  1. 判斷頁存在 并且 沒滿 直接添加obj
  2. 判斷頁存在 并且 滿了 創(chuàng)建新頁割按,添加obj
  3. 頁不存時膨报,創(chuàng)建頁添加obj
  • 查看add(id obj)
 id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

這里可以很明顯的看出next指向了最新加入的obj后面的位置,也就是下一個obj地址

  • 查看autoreleaseFullPage
static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        ASSERT(page == hotPage());
        ASSERT(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }
  1. 不斷遍歷子節(jié)點适荣,查找一個沒有滿的page现柠,如果沒有則新建一個page,上一個pagechild指向new page
  2. page設(shè)置為hot弛矛,加入obj

接下來查看pop方法够吩,看page中的數(shù)據(jù)如何出來。

  • 搜索objc_autoreleasePoolPop
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
  • 點擊查看pop方法
 static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;//對空頁面的處理
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            page = pageForPointer(token);
        }

        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {//不是邊界
            if (stop == page->begin()  &&  !page->parent) {//等于begin()  沒有父節(jié)點 不用處理
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);//
            }//這是一個容錯處理
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        return popPage<false>(token, page, stop);
    }

真正要研究的是最下面的popPage方法

  • 查看popPage
static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();
        //遍歷page中的所有 obj丈氓,release obj
        page->releaseUntil(stop);

        // memory: delete empty children 銷毀所有的空頁面
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
             //page 為空銷毀自身周循,設(shè)置父節(jié)點為hot page
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            //沒有父節(jié)點,銷毀當前page
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
            //page 容量少于一半  child銷毀
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {//page的子節(jié)點 還有子節(jié)點  就銷毀
                page->child->child->kill();
            }
        }
    }

在這個方法中銷毀空page

  • 查看releaseUntil
void releaseUntil(id *stop) 
    {
        //一直循環(huán)万俗,stop為哨兵對象的值 一直pop和release obj 直到哨兵對象為止
        while (this->next != stop) {
            
            AutoreleasePoolPage *page = hotPage();

           //如果page為空湾笛,指向他的parent
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            id obj = *--page->next;//obj 賦值為page中的最后一個元素
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));//清理page->next
            page->protect();

            if (obj != POOL_BOUNDARY) {
                objc_release(obj);//obj  release
            }
        }
        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
#endif
    }

在這個方法中poprelease obj對象

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市闰歪,隨后出現(xiàn)的幾起案子嚎研,更是在濱河造成了極大的恐慌,老刑警劉巖库倘,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件临扮,死亡現(xiàn)場離奇詭異,居然都是意外死亡于樟,警方通過查閱死者的電腦和手機公条,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迂曲,“玉大人靶橱,你說我怎么就攤上這事÷放酰” “怎么了关霸?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杰扫。 經(jīng)常有香客問我队寇,道長,這世上最難降的妖魔是什么章姓? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任佳遣,我火速辦了婚禮识埋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘零渐。我一直安慰自己窒舟,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布诵盼。 她就那樣靜靜地躺著惠豺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪风宁。 梳的紋絲不亂的頭發(fā)上洁墙,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音戒财,去河邊找鬼热监。 笑死,一個胖子當著我的面吹牛饮寞,可吹牛的內(nèi)容都是我干的狼纬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼骂际,長吁一口氣:“原來是場噩夢啊……” “哼疗琉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起歉铝,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盈简,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后太示,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柠贤,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年类缤,在試婚紗的時候發(fā)現(xiàn)自己被綠了臼勉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡餐弱,死狀恐怖宴霸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膏蚓,我是刑警寧澤瓢谢,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏算墨。R本人自食惡果不足惜碰辅,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一采郎、第九天 我趴在偏房一處隱蔽的房頂上張望千所。 院中可真熱鬧,春花似錦蒜埋、人聲如沸真慢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至管嬉,卻和暖如春皂林,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚯撩。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工础倍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胎挎。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓沟启,卻偏偏與公主長得像,于是被迫代替她去往敵國和親犹菇。 傳聞我的和親對象是個殘疾皇子德迹,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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