OC的內(nèi)存管理方式

MRC

手動(dòng)內(nèi)存管理,每一次的retain,new,alloc都對(duì)應(yīng)一次release,autorelease;

ARC

自動(dòng)引用計(jì)數(shù),由編譯器在編譯期間用更底層的C接口實(shí)現(xiàn)retain/release/autorelease,不需要手動(dòng)釋放。(底層的原理和接口api待會(huì)講解)

ARC的規(guī)則

當(dāng)對(duì)象創(chuàng)建時(shí),引用計(jì)數(shù)為1;當(dāng)一個(gè)強(qiáng)指針(strong或者retain)指向該對(duì)象時(shí),引用計(jì)數(shù)+1;當(dāng)強(qiáng)指針不在指向該對(duì)象時(shí),引用計(jì)數(shù)-1;當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí),說明這個(gè)對(duì)象不被任何指針指向,可以銷毀,回收內(nèi)存。

ARC的內(nèi)部實(shí)現(xiàn)

ARC背后的引用計(jì)數(shù)主要依賴于三個(gè)方法:

  • retain:增加引用計(jì)數(shù)
  • release:降低引用計(jì)數(shù),引用計(jì)數(shù)為0的時(shí)候,釋放對(duì)象
  • autorelease:在當(dāng)前的autorelease pool結(jié)束后,降低引用計(jì)數(shù)

下面我們來看看runtime的源碼。

- (id)retain {
    return ((id)self)->rootRetain();
}
inline id objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}
id objc_object::sidetable_retain()
{
    //獲取table
    SideTable& table = SideTables()[this];
    //加鎖
    table.lock();
    //獲取引用計(jì)數(shù)
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
         //增加引用計(jì)數(shù)
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    //解鎖
    table.unlock();
    return (id)this;
}

我們可以看到底層的C語(yǔ)言,retain方法實(shí)際是調(diào)用sidetable_retain();方法,在該方法中,有一個(gè)SideTable的結(jié)構(gòu)體來存儲(chǔ)引用計(jì)數(shù),下面來看一下這個(gè)結(jié)構(gòu)體的組成。

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
struct SideTable {
       spinlock_t slock;
       RefcountMap refcnts;
       weak_table_t weak_table;
       //省略
}

可以看到,這個(gè)數(shù)據(jù)結(jié)構(gòu)就是存儲(chǔ)了一個(gè)自旋鎖,一個(gè)引用計(jì)數(shù)map。這個(gè)引用計(jì)數(shù)的map以對(duì)象的地址為key,引用計(jì)數(shù)作為value。到這里,retain的底層實(shí)現(xiàn)就很清楚了。
再來看看release的實(shí)現(xiàn):

SideTable& table = SideTables()[this];
    bool do_dealloc = false;
    table.lock();
    //找到對(duì)應(yīng)地址的
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) { //找不到的話,執(zhí)行dellloc
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {//引用計(jì)數(shù)小于閾值,dealloc
        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) {
        //執(zhí)行dealloc
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;

很簡(jiǎn)單的邏輯:查找map,對(duì)引用計(jì)數(shù)減1,如果引用計(jì)數(shù)小于閾值或者找不到引用計(jì)數(shù),則調(diào)用dealloc。

我們知道非autorelease對(duì)象在超出作用域的時(shí)候就會(huì)被release掉,而autorelease對(duì)象在什么情況下會(huì)被釋放呢?

  • 手動(dòng)干預(yù)釋放:指定autorelease pool,在autorelease pool執(zhí)行完畢時(shí),對(duì)對(duì)象進(jìn)行釋放;
  • 自動(dòng)釋放:當(dāng)runloop迭代退出的時(shí)候,自動(dòng)對(duì)對(duì)象進(jìn)行釋放。
    所以autorelease會(huì)延遲對(duì)象的release。

下面介紹一下autorelease pool。

Autorelease Pool

事實(shí)上在iOS 程序啟動(dòng)之后,主線程會(huì)啟動(dòng)一個(gè)Runloop,這個(gè)Runloop在每一次循環(huán)是被自動(dòng)釋放池包裹的,在合適的時(shí)候?qū)Τ刈舆M(jìn)行清空。

也就是說AutoreleasePool創(chuàng)建是在一個(gè)RunLoop事件開始之前(push),AutoreleasePool釋放是在一個(gè)RunLoop事件即將結(jié)束之前(pop)。AutoreleasePool里的Autorelease對(duì)象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease對(duì)象的釋放是在AutoreleasePool釋放時(shí)。

autorelease pool的底層實(shí)現(xiàn):
Autorelease Pool主要是三個(gè)方法實(shí)現(xiàn):
objc_autoreleasePush();->objc_autorelease();->objc_autoreleasePop();
研究源碼可以發(fā)現(xiàn),一個(gè)線程的autorelease pool是一個(gè)指針棧,棧中存放的指針指向需要release的對(duì)象或者POOL_SENTINEL(哨兵對(duì)象,用于分隔autorelease pool,現(xiàn)在叫POOL_BOUNDARY)。
棧中指向POOL_SENTINEL(即POOL_BOUNDARY)的指針就是autorelease pool的一個(gè)標(biāo)記。當(dāng)autorelease pool進(jìn)行出棧操作,每一個(gè)比這個(gè)哨兵對(duì)象晚進(jìn)棧的對(duì)象,都會(huì)release。

下面我們來看一下源碼:
在終端中使用clang -rewrite-objc 命令將OC的main函數(shù)重寫成C++的實(shí)現(xiàn):

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

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
    }

    return 0;
}

可以看到一個(gè)__AtAutoreleasePool類型的局部變量__autoreleasepool,__AtAutoreleasePool的定義如下:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

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

這里一個(gè)是構(gòu)造函數(shù)一個(gè)是析構(gòu)函數(shù)(局部變量的構(gòu)造函數(shù)是在程序執(zhí)行到聲明對(duì)象的位置時(shí)調(diào)用,而析構(gòu)函數(shù)是在程序執(zhí)行到離開這個(gè)對(duì)象的作用域時(shí)調(diào)用)。

因此,自動(dòng)釋放池的執(zhí)行過程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void )。

來看一下push和pop兩個(gè)方法的實(shí)現(xiàn):

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

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

下面介紹一下AutoreleasePoolPage的實(shí)現(xiàn)。

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

AutoreleasePoolPage介紹

是c++中的一個(gè)類,它在NSObject.mm中的定義如下:

class AutoreleasePoolPage {
#   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;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

介紹一下參數(shù):

  • magic 檢查校驗(yàn)完整性的變量
  • next 指向新加入的autorelease對(duì)象
  • thread page 當(dāng)前所在的線程,AutoreleasePool是按線程一一對(duì)應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
  • parent 父節(jié)點(diǎn) 指向前一個(gè)page
  • child 子節(jié)點(diǎn) 指向下一個(gè)page
  • depth 鏈表的深度,節(jié)點(diǎn)個(gè)數(shù)
  • hiwat 數(shù)據(jù)容納的上限(high water mark)
  • EMPTY_POOL_PLACEHOLDER 空池占位
  • POOL_BOUNDARY 是一個(gè)邊界對(duì)象nil,之前變量名是POOL_SENTINEL哨兵對(duì)象,用來區(qū)分每個(gè)page即每個(gè)autoreleasepoolpage邊界
  • PAGE_MAX_SIZE = 4096
  • COUNT 一個(gè)page里對(duì)象數(shù)

雙向鏈表

自動(dòng)釋放池并沒有單獨(dú)的結(jié)構(gòu),而是由若干個(gè)AutoreleasePoolPage以雙向鏈表的形式組合而成的棧結(jié)構(gòu)(對(duì)應(yīng)parent指針和child指針)
parent指向前一個(gè)page,child指向下一個(gè)page。
當(dāng)一個(gè)page的空間被占滿時(shí),會(huì)新建一個(gè)page對(duì)象,連接鏈表,后來的autorelease對(duì)象在新的page中加入。

objc_autoreleasePoolPush

autoreleasePoolPush.jpg

調(diào)用objc_autoreleasePoolPush方法時(shí)會(huì)把邊界對(duì)象放進(jìn)棧頂,然后返回邊界對(duì)象,用于objc_autoreleasePoolPop。

atautoreleasepoolobj = objc_autoreleasePoolPush();
///atautoreleasepoolobj就是返回的邊界對(duì)象(POOL_BOUNDARY)

push實(shí)現(xiàn)如下:

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

static inline void *push() {
   return autoreleaseFast(POOL_BOUNDARY);
}

在這里會(huì)進(jìn)入一個(gè)比較關(guān)鍵的方法autoreleaseFast,并傳入邊界對(duì)象;

/*
有 hotPage 并且當(dāng)前 page 不滿,調(diào)用 page->add(obj) 方法將對(duì)象添加至 AutoreleasePoolPage 的棧中
有 hotPage 并且當(dāng)前 page 已滿,調(diào)用 autoreleaseFullPage 初始化一個(gè)新的頁(yè),調(diào)用 page->add(obj) 方法將對(duì)象添加至 AutoreleasePoolPage 的棧中
無 hotPage,調(diào)用 autoreleaseNoPage 創(chuàng)建一個(gè) hotPage,調(diào)用 page->add(obj) 方法將對(duì)象添加至 AutoreleasePoolPage 的棧中
*/
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);
   }
}

最后都會(huì)調(diào)用page->add(obj)將對(duì)象添加到自動(dòng)釋放池中。hotPage可以理解為當(dāng)前正在使用的AutoreleasePoolPage。

AutoreleasePoolPage::autorelease(id obj)

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

    return rootAutorelease2();
}

__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
   id *dest __unused = autoreleaseFast(obj);
   return obj;
}

autorelease方法和push方法一樣,關(guān)鍵代碼都是調(diào)用autoreleaseFast函數(shù)向自動(dòng)釋放池的鏈表?xiàng)V刑砑右粋€(gè)對(duì)象,不過push函數(shù)入棧的是一個(gè)邊界對(duì)象,而autorelease函數(shù)入棧的是需要加入釋放池的對(duì)象。

objc_autoreleasePoolPop

autoreleasePoolPop.jpg

自動(dòng)釋放池釋放是傳入push返回的邊界對(duì)象,然后將邊界對(duì)象指向的這一頁(yè)AutoreleasePoolPage內(nèi)的對(duì)象釋放。

objc_autoreleasePoolPop(atautoreleasepoolobj);

AutoreleasePoolPage::pop()實(shí)現(xiàn):

static inline void pop(void *token)   // token指針指向棧頂?shù)牡刂?{
    AutoreleasePoolPage *page;
    id *stop;

    page = pageForPointer(token);   // 通過棧頂?shù)牡刂氛业綄?duì)應(yīng)的page
    stop = (id *)token;
    if (DebugPoolAllocation  &&  *stop != POOL_SENTINEL) {
        // This check is not valid with DebugPoolAllocation off
        // after an autorelease with a pool page but no pool in place.
        _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                    token);
    }

    if (PrintPoolHiwat) printHiwat();   // 記錄最高水位標(biāo)記

    page->releaseUntil(stop);   // 從棧頂開始操作出棧,并向棧中的對(duì)象發(fā)送release消息,直到遇到第一個(gè)哨兵對(duì)象

    // memory: delete empty children
    // 刪除空掉的節(jié)點(diǎn)
    if (DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        // special case: delete everything for pop(top) 
        // when debugging missing autorelease pools
        page->kill();
        setHotPage(nil);
    } 
    else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

該過程主要分為兩步:

  • page->releaseUntil(stop),對(duì)棧頂(page->next)到stop地址(POOL_SENTINEL)之間的所有對(duì)象調(diào)用objc_release(),進(jìn)行引用計(jì)數(shù)減1
  • 清空page對(duì)象page->kill()

小結(jié)

  • 自動(dòng)釋放池是多個(gè)AutoreleasePoolPage以雙向鏈表的形式組成的
  • push時(shí)會(huì)將邊界對(duì)象加入AutoreleasePoolPage的棧中,作為一個(gè)標(biāo)識(shí),且返回該邊界對(duì)象;
  • 當(dāng)對(duì)象調(diào)用autorelease方法時(shí),會(huì)將對(duì)象加入AutoreleasePoolPage的棧中;
  • pop時(shí)傳入邊界對(duì)象,然后對(duì)page中的對(duì)象發(fā)送release消息;

覺得有用,請(qǐng)幫忙點(diǎn)亮紅心


Better Late Than Never!
努力是為了當(dāng)機(jī)會(huì)來臨時(shí)不會(huì)錯(cuò)失機(jī)會(huì)。
共勉!

最后編輯于
?著作權(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ù)。

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