iOS 自動釋放池原理

簡介

自動釋放池(autoreleasepool)是OC的一種內(nèi)存自動回收機制。正常情況下,創(chuàng)建的變量超出作用域時釋放,自動釋放池可以延遲對象的釋放。

原理

OC代碼
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

使用clang命令將OC代碼重寫成C++

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc  main.m
C++代碼
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

__AtAutoreleasePool實際是一個結(jié)構(gòu)體,內(nèi)部首先執(zhí)行objc_autoreleasePoolPush(),然后在調(diào)用objc_autoreleasePoolPop(atautoreleasepoolobj)

struct __AtAutoreleasePool {
 **構(gòu)造函數(shù),在創(chuàng)建結(jié)構(gòu)體時調(diào)用**
  __AtAutoreleasePool() {
  atautoreleasepoolobj = objc_autoreleasePoolPush();
}

**析構(gòu)函數(shù),在結(jié)構(gòu)體銷毀的時候調(diào)用**
  ~__AtAutoreleasePool() {
  objc_autoreleasePoolPop(atautoreleasepoolobj);
}
  void * atautoreleasepoolobj;
};
objc4-818源碼

源碼中可以看出,objc_autoreleasePoolPush()函數(shù)內(nèi)調(diào)用了AutoreleasePoolPagepush()方法,objc_autoreleasePoolPop()則調(diào)用了AutoreleasePoolPagepop()方法。
也就是說,需要Autorelease的對象,都是由AutoreleasePoolPage對象來管理的。

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

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

AutoreleasePoolPage對象源碼:

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
        uintptr_t ptr: 48;
        uintptr_t count: 16;

        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
    static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif

    ////用來校驗AutoreleasePoolPage的結(jié)構(gòu)是否完整
    magic_t const magic; //16字節(jié)
    //下次新添加的autoreleased對象的位置,初始化時指向begin()
    __unsafe_unretained id *next;//8字節(jié)
    //當(dāng)前線程
    pthread_t const thread;//8字節(jié)
    //指向父節(jié)點,即上一個頁面,第一個頁面的parent值為nil
    AutoreleasePoolPage * const parent;//8字節(jié)
    //指向子節(jié)點,即下一個頁面,最后一個頁面的child值為nil
    AutoreleasePoolPage *child;//8字節(jié)
    //表示頁面深度,從0開始,往后遞增1
    uint32_t const depth; //4字節(jié)
    //high water mark,表示最大入棧數(shù)量標(biāo)記
    uint32_t hiwat;
 //初始化
    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)
    {
    }
};

一個AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存,除了存放自己內(nèi)部變量以外,剩下的內(nèi)存空間就用來存放需要Autorelease的對象地址。
所有AutoreleasePoolPage對象都是以棧為節(jié)點通過雙向鏈表的形式連接在一起

AutoreleasePool是由多個AutoreleasePoolPage對象組成的,以雙向鏈表組成,其中parent指針指向上一個AutoreleasePoolPage對象,child指針指向下一個AutoreleasePoolPage對象,如下圖:

AutoreleasePoolPage管理Autorelease對象的過程
  • AutoreleasePoolPage對象中的next指針指向下一個能存放Autorelease對象地址的區(qū)域。

  • 上文講到,push()調(diào)用時,內(nèi)部會將一個POOL_BOUNDARY哨兵入棧,并返回其存放的內(nèi)存地址,然后next指針指向POOL_BOUNDARY后面第一個內(nèi)存地址。(POOL_BOUNDARY作用應(yīng)該是起到標(biāo)記的作用)

  • 當(dāng)有Autorelease對象入棧時,會存放在next指針指向的內(nèi)存空間,然后next指針指向Autorelease對象地址后面的內(nèi)存空間
    如下圖:

  • 當(dāng)前的AutoreleasePoolPage對象存放滿了,就會創(chuàng)建新的AutoreleasePoolPage對象,用來存放Autorelease對象。

  • 但自動釋放池結(jié)束時,會調(diào)用objc_autoreleasePoolPop()函數(shù),然后調(diào)用AutoreleasePoolPagepop()方法,在pop()方法內(nèi)部會通過next指針找到最后一個入棧的Autorelease對象,開始發(fā)送release消息進行釋放,直到找到POOL_BOUNDARY為止,這樣釋放池里面的Autorelease對象就能全部釋放。

Autorelease對象添加過程

當(dāng)我們調(diào)用autorelease方法時,底層做了哪些操作

**autorelease 函數(shù)內(nèi)部實現(xiàn)**
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

rootAutorelease內(nèi)部實現(xiàn)

id _objc_rootAutorelease(id obj)
{
    assert(obj);
    return obj->rootAutorelease();
}

inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

rootAutorelease2函數(shù)內(nèi)部實現(xiàn)

id  objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

AutoreleasePoolPage 對象的autorelease 函數(shù)實現(xiàn)

public:
    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 函數(shù)內(nèi)部實現(xiàn)

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

通過源碼可以看到,當(dāng)調(diào)用autorelease方法時:

  • 底層調(diào)用rootAutorelease函數(shù),然后調(diào)用rootAutorelease2函數(shù),然后調(diào)用AutoreleasePoolPage對象的autorelease方法
  • AutoreleasePoolPage對象的autorelease方法調(diào)用autoreleaseFast函數(shù),函數(shù)內(nèi)將Autorelease對象添加到AutoreleasePoolPage中。

自動釋放池釋放時機

系統(tǒng)自動釋放

自動釋放池寄生于Runloop:程序啟動后,主線程會注冊兩個Observer,回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()
1、監(jiān)測Enter(即將進入Loop)狀態(tài),回調(diào)內(nèi)會調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動釋放池,優(yōu)先級最高。
2、監(jiān)測BeforeWaiting(準(zhǔn)備進入休眠)和Exit(即將推出Loop)。BeforeWaiting時調(diào)用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit_objc_autoreleasePoolPop() 來釋放自動釋放池;優(yōu)先級最低。
總結(jié):

  • 程序啟動時,Runloop啟動,創(chuàng)建第一個自動釋放池,事件優(yōu)先級最高。
  • Runloop即將進入休眠時,清理需要釋放的對象,調(diào)用pop(),事件優(yōu)先級最低。
  • Runloop退出時,銷毀最后一個自動釋放池。
  • Runloop休眠時會釋放舊的并創(chuàng)建新的自動釋放池。
手動釋放

當(dāng)特定場景時我們自己創(chuàng)建自動釋放池時,在當(dāng)前作用域大括號結(jié)束時釋放。

手動使用AutoreleasePool場景

  • 寫給予命令行的程序時,就是沒有UI框架;
  • 寫循環(huán),循環(huán)里邊包含了大量臨時創(chuàng)建的對象;
  • 創(chuàng)建了新的線程;
  • 長時間在后臺運行的任務(wù)

以上信息只用于本人學(xué)習(xí)使用,基本抄的此鏈接中的內(nèi)容,特此聲明

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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