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

調(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

自動(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ì)。
共勉!