前言
作為 iOS 開(kāi)發(fā)者蚜锨,在面試過(guò)程中經(jīng)常會(huì)碰到這樣一個(gè)問(wèn)題:在 ARC 環(huán)境下autorelease
對(duì)象在什么時(shí)候釋放?如果你還不知道怎么回答沙绝,或者你只有比較模糊的概念搏明,那么你絕對(duì)不能錯(cuò)過(guò)本文。
本文將通過(guò)Runtime objc4-756.2
版本源碼闪檬、macOS 與 iOS 工程示例來(lái)分析@autoreleasepool
的底層原理星著。并在最后針對(duì)有關(guān)autorelease
和@autoreleasepool
的一些問(wèn)題進(jìn)行解答。
目錄
- 1. 簡(jiǎn)單聊聊 ARC 與 MRC
- 2. 自動(dòng)釋放池
- 3. 原理分析
- 4. 查看自動(dòng)釋放池的情況
- 5. 使用 macOS 工程示例分析
- 6. 使用 iOS 工程示例分析
?系統(tǒng)干預(yù)釋放
?RunLoop 與 @autoreleasepool
?手動(dòng)干預(yù)釋放- 相關(guān)問(wèn)題
Q:ARC 環(huán)境下粗悯,autorelease 對(duì)象在什么時(shí)候釋放虚循?
Q:ARC 環(huán)境下,需不需要手動(dòng)添加 @autoreleasepool样傍?
1. 簡(jiǎn)單聊聊 ARC 與 MRC
蘋(píng)果在 iOS 5 中引入了ARC(Automatic Reference Counting)
自動(dòng)引用計(jì)數(shù)內(nèi)存管理技術(shù)横缔,通過(guò)LLVM
編譯器和Runtime
協(xié)作來(lái)進(jìn)行自動(dòng)管理內(nèi)存。LLVM
編譯器會(huì)在編譯時(shí)在合適的地方為 OC 對(duì)象插入retain
衫哥、release
和autorelease
代碼茎刚,省去了在MRC(Manual Reference Counting)
手動(dòng)引用計(jì)數(shù)下手動(dòng)插入這些代碼的工作,減輕了開(kāi)發(fā)者的工作量炕檩。
在MRC
下斗蒋,當(dāng)我們不需要一個(gè)對(duì)象的時(shí)候,要調(diào)用release
或autorelease
方法來(lái)釋放它笛质。調(diào)用release
會(huì)立即讓對(duì)象的引用計(jì)數(shù)減 1 泉沾,如果此時(shí)對(duì)象的引用計(jì)數(shù)為 0,對(duì)象就會(huì)被銷(xiāo)毀妇押。調(diào)用autorelease
會(huì)將該對(duì)象添加進(jìn)自動(dòng)釋放池中跷究,它會(huì)在一個(gè)恰當(dāng)?shù)臅r(shí)刻自動(dòng)給對(duì)象調(diào)用release
,所以autorelease
相當(dāng)于延遲了對(duì)象的釋放敲霍。
在ARC
下俊马,autorelease
方法已被禁用,我們可以使用__autoreleasing
修飾符修飾對(duì)象將對(duì)象注冊(cè)到自動(dòng)釋放池中肩杈。詳情請(qǐng)參閱《iOS - 老生常談內(nèi)存管理(三):ARC 面世 —— 所有權(quán)修飾符》
柴我。
2. 自動(dòng)釋放池
簡(jiǎn)介
官方文檔
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create “l(fā)ocal” autorelease pools to help to minimize the peak memory footprint.
以上是蘋(píng)果對(duì)自動(dòng)釋放池的一段介紹,其意思為:AppKit 和 UIKit 框架在事件循環(huán)(RunLoop
)的每次循環(huán)開(kāi)始時(shí)扩然,在主線程創(chuàng)建一個(gè)自動(dòng)釋放池艘儒,并在每次循環(huán)結(jié)束時(shí)銷(xiāo)毀它,在銷(xiāo)毀時(shí)釋放自動(dòng)釋放池中的所有autorelease
對(duì)象。通常情況下我們不需要手動(dòng)創(chuàng)建自動(dòng)釋放池界睁,但是如果我們?cè)谘h(huán)中創(chuàng)建了很多臨時(shí)的autorelease
對(duì)象觉增,則手動(dòng)創(chuàng)建自動(dòng)釋放池來(lái)管理這些對(duì)象可以很大程度地減少內(nèi)存峰值。
創(chuàng)建一個(gè)自動(dòng)釋放池
- 在
MRC
下翻斟,可以使用NSAutoreleasePool
或者@autoreleasepool
逾礁。建議使用@autoreleasepool
,蘋(píng)果說(shuō)它比NSAutoreleasePool
快大約六倍访惜。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release]; // [pool drain]
Q: 釋放
NSAutoreleasePool
對(duì)象嘹履,使用[pool release]
與[pool drain]
的區(qū)別?
Objective-C 語(yǔ)言本身是支持 GC 機(jī)制的疾牲,但有平臺(tái)局限性植捎,僅限于 MacOS 開(kāi)發(fā)中衙解,iOS 開(kāi)發(fā)用的是 RC 機(jī)制阳柔。在 iOS 的 RC 環(huán)境下[pool release]
和[pool drain]
效果一樣,但在 GC 環(huán)境下drain
會(huì)觸發(fā) GC 而release
不做任何操作蚓峦。使用[pool drain]
更佳舌剂,一是它的功能對(duì)系統(tǒng)兼容性更強(qiáng),二是這樣可以跟普通對(duì)象的release
區(qū)別開(kāi)暑椰。(注意:蘋(píng)果在引入ARC
時(shí)稱(chēng)霍转,已在 OS X Mountain Lion v10.8 中棄用GC
機(jī)制,而使用ARC
替代)
- 而在
ARC
下一汽,已經(jīng)禁止使用NSAutoreleasePool
類(lèi)創(chuàng)建自動(dòng)釋放池避消,只能使用@autoreleasepool
。
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
3. 原理分析
下面我們先通過(guò)macOS
工程來(lái)分析@autoreleasepool
的底層原理召夹。
macOS
工程中的main()
函數(shù)什么都沒(méi)做岩喷,只是放了一個(gè)@autoreleasepool
。
int main(int argc, const char * argv[]) {
@autoreleasepool {}
return 0;
}
__AtAutoreleasePool
通過(guò) Clang clang -rewrite-objc main.m
將以上代碼轉(zhuǎn)換為 C++ 代碼监憎。
struct __AtAutoreleasePool {
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool; }
return 0;
}
可以看到:
-
@autoreleasepool
底層是創(chuàng)建了一個(gè)__AtAutoreleasePool
結(jié)構(gòu)體對(duì)象纱意; - 在創(chuàng)建
__AtAutoreleasePool
結(jié)構(gòu)體時(shí)會(huì)在構(gòu)造函數(shù)中調(diào)用objc_autoreleasePoolPush()
函數(shù),并返回一個(gè)atautoreleasepoolobj
(POOL_BOUNDARY
存放的內(nèi)存地址鲸阔,下面會(huì)講到)偷霉; - 在釋放
__AtAutoreleasePool
結(jié)構(gòu)體時(shí)會(huì)在析構(gòu)函數(shù)中調(diào)用objc_autoreleasePoolPop()
函數(shù),并將atautoreleasepoolobj
傳入褐筛。
AutoreleasePoolPage
下面我們進(jìn)入Runtime objc4
源碼查看以上提到的兩個(gè)函數(shù)的實(shí)現(xiàn)类少。
// NSObject.mm
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可以得知,objc_autoreleasePoolPush()
和objc_autoreleasePoolPop()
兩個(gè)函數(shù)其實(shí)是調(diào)用了AutoreleasePoolPage
類(lèi)的兩個(gè)類(lèi)方法push()
和pop()
渔扎。所以@autoreleasepool
底層就是使用AutoreleasePoolPage
類(lèi)來(lái)實(shí)現(xiàn)的硫狞。
下面我們來(lái)看一下AutoreleasePoolPage
類(lèi)的定義:
// objc4-756.2
// NSObject.mm
class AutoreleasePoolPage
{
# define EMPTY_POOL_PLACEHOLDER ((id*)1) // EMPTY_POOL_PLACEHOLDER:表示一個(gè)空自動(dòng)釋放池的占位符
# define POOL_BOUNDARY nil // POOL_BOUNDARY:哨兵對(duì)象
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 用來(lái)標(biāo)記已釋放的對(duì)象
static size_t const SIZE = // 每個(gè) Page 對(duì)象占用 4096 個(gè)字節(jié)內(nèi)存
#if PROTECT_AUTORELEASEPOOL // PAGE_MAX_SIZE = 4096
PAGE_MAX_SIZE; // must be muliple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id); // Page 的個(gè)數(shù)
magic_t const magic; // 用來(lái)校驗(yàn) Page 的結(jié)構(gòu)是否完整
id *next; // 指向下一個(gè)可存放 autorelease 對(duì)象地址的位置,初始化指向 begin()
pthread_t const thread; // 指向當(dāng)前線程
AutoreleasePoolPage * const parent; // 指向父結(jié)點(diǎn),首結(jié)點(diǎn)的 parent 為 nil
AutoreleasePoolPage *child; // 指向子結(jié)點(diǎn)妓忍,尾結(jié)點(diǎn)的 child 為 nil
uint32_t const depth; // Page 的深度虏两,從 0 開(kāi)始遞增
uint32_t hiwat;
......
}
備注: 本文使用的是
objc4-756.2
版本源碼進(jìn)行分析。在objc4-779.1
版本中世剖,AutoreleasePoolPage
繼承自AutoreleasePoolPageData
定罢,如下。不過(guò)原理不變旁瘫,不影響分析祖凫。
// objc4-779.1
// NSObject.mm
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
private:
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const COUNT = SIZE / sizeof(id);
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
// SIZE-sizeof(*this) bytes of contents follow
......
}
// NSObject-internal.h
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
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)
{
}
};
整個(gè)程序運(yùn)行過(guò)程中,可能會(huì)有多個(gè)AutoreleasePoolPage
對(duì)象酬凳。從它的定義可以得知:
- 自動(dòng)釋放池(即所有的
AutoreleasePoolPage
對(duì)象)是以棧
為結(jié)點(diǎn)通過(guò)雙向鏈表
的形式組合而成惠况; - 自動(dòng)釋放池與線程一一對(duì)應(yīng);
- 每個(gè)
AutoreleasePoolPage
對(duì)象占用4096
字節(jié)內(nèi)存宁仔,其中56
個(gè)字節(jié)用來(lái)存放它內(nèi)部的成員變量稠屠,剩下的空間(4040
個(gè)字節(jié))用來(lái)存放autorelease
對(duì)象的地址。
其內(nèi)存分布圖如下:
下面我們通過(guò)源碼來(lái)分析push()
翎苫、pop()
以及autorelease
方法的實(shí)現(xiàn)权埠。
POOL_BOUNDARY
在分析這些方法之前,先介紹一下POOL_BOUNDARY
煎谍。
-
POOL_BOUNDARY
的前世叫做POOL_SENTINEL
攘蔽,稱(chēng)為哨兵對(duì)象或者邊界對(duì)象; -
POOL_BOUNDARY
用來(lái)區(qū)分不同的自動(dòng)釋放池呐粘,以解決自動(dòng)釋放池嵌套的問(wèn)題满俗; - 每當(dāng)創(chuàng)建一個(gè)自動(dòng)釋放池,就會(huì)調(diào)用
push()
方法將一個(gè)POOL_BOUNDARY
入棧作岖,并返回其存放的內(nèi)存地址唆垃; - 當(dāng)往自動(dòng)釋放池中添加
autorelease
對(duì)象時(shí),將autorelease
對(duì)象的內(nèi)存地址入棧鳍咱,它們前面至少有一個(gè)POOL_BOUNDARY
降盹; - 當(dāng)銷(xiāo)毀一個(gè)自動(dòng)釋放池時(shí),會(huì)調(diào)用
pop()
方法并傳入一個(gè)POOL_BOUNDARY
谤辜,會(huì)從自動(dòng)釋放池中最后一個(gè)對(duì)象開(kāi)始蓄坏,依次給它們發(fā)送release
消息,直到遇到這個(gè)POOL_BOUNDARY
丑念。
push
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 出錯(cuò)時(shí)進(jìn)入調(diào)試狀態(tài)
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY); // 傳入 POOL_BOUNDARY 哨兵對(duì)象
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
當(dāng)創(chuàng)建一個(gè)自動(dòng)釋放池時(shí)涡戳,會(huì)調(diào)用push()
方法。push()
方法中調(diào)用了autoreleaseFast()
方法并傳入了POOL_BOUNDARY
哨兵對(duì)象脯倚。
下面我們來(lái)看一下autoreleaseFast()
方法的實(shí)現(xiàn):
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 雙向鏈表中的最后一個(gè) Page
if (page && !page->full()) { // 如果當(dāng)前 Page 存在且未滿
return page->add(obj); // 將 autorelease 對(duì)象入棧渔彰,即添加到當(dāng)前 Page 中嵌屎;
} else if (page) { // 如果當(dāng)前 Page 存在但已滿
return autoreleaseFullPage(obj, page); // 創(chuàng)建一個(gè)新的 Page,并將 autorelease 對(duì)象添加進(jìn)去
} else { // 如果當(dāng)前 Page 不存在恍涂,即還沒(méi)創(chuàng)建過(guò) Page
return autoreleaseNoPage(obj); // 創(chuàng)建第一個(gè) Page宝惰,并將 autorelease 對(duì)象添加進(jìn)去
}
}
autoreleaseFast()
中先是調(diào)用了hotPage()
方法獲得未滿的Page
,從AutoreleasePoolPage
類(lèi)的定義可知再沧,每個(gè)Page
的內(nèi)存大小為4096
個(gè)字節(jié)尼夺,每當(dāng)Page
滿了的時(shí)候,就會(huì)創(chuàng)建一個(gè)新的Page
炒瘸。hotPage()
方法就是用來(lái)獲得這個(gè)新創(chuàng)建的未滿的Page
淤堵。
autoreleaseFast()
在執(zhí)行過(guò)程中有三種情況:
- ① 當(dāng)前
Page
存在且未滿時(shí),通過(guò)page->add(obj)
將autorelease
對(duì)象入棧顷扩,即添加到當(dāng)前Page
中拐邪; - ② 當(dāng)前
Page
存在但已滿時(shí),通過(guò)autoreleaseFullPage(obj, page)
創(chuàng)建一個(gè)新的Page
隘截,并將autorelease
對(duì)象添加進(jìn)去扎阶; - ③ 當(dāng)前
Page
不存在,即還沒(méi)創(chuàng)建過(guò)Page
技俐,通過(guò)autoreleaseNoPage(obj)
創(chuàng)建第一個(gè)Page
乘陪,并將autorelease
對(duì)象添加進(jìn)去。
下面我們來(lái)看一下以上提到的三個(gè)方法的實(shí)現(xiàn):
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
page->add(obj)
其實(shí)就是將autorelease
對(duì)象添加到Page
中的next
指針?biāo)赶虻奈恢玫窭蓿?code>next指針指向這個(gè)對(duì)象的下一個(gè)位置,然后將該對(duì)象的位置返回贱勃。
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);
}
autoreleaseFullPage()
方法中通過(guò)while
循環(huán)井赌,通過(guò)Page
的child
指針找到最后一個(gè)Page
。
- 如果最后一個(gè)
Page
未滿贵扰,就通過(guò)page->add(obj)
將autorelease
對(duì)象添加到最后一個(gè)Page
中仇穗; - 如果最后一個(gè)
Page
已滿,就創(chuàng)建一個(gè)新的Page
并將該Page
設(shè)置為hotPage
戚绕,通過(guò)page->add(obj)
將autorelease
對(duì)象添加進(jìn)去纹坐。
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
autoreleaseNoPage()
方法中會(huì)創(chuàng)建第一個(gè)Page
。該方法會(huì)判斷是否有空的自動(dòng)釋放池存在舞丛,如果沒(méi)有會(huì)通過(guò)setEmptyPoolPlaceholder()
生成一個(gè)占位符耘子,表示一個(gè)空的自動(dòng)釋放池。接著創(chuàng)建第一個(gè)Page
球切,設(shè)置它為hotPage
谷誓。最后將一個(gè)POOL_BOUNDARY
添加進(jìn)Page
中,并返回POOL_BOUNDARY
的下一個(gè)位置吨凑。
小結(jié):以上就是
push
操作的實(shí)現(xiàn)捍歪,往自動(dòng)釋放池中添加一個(gè)POOL_BOUNDARY
户辱,并返回它存放的內(nèi)存地址。接著每有一個(gè)對(duì)象調(diào)用autorelease
方法糙臼,會(huì)將它的內(nèi)存地址添加進(jìn)自動(dòng)釋放池中庐镐。
autorelease
autorelease
方法的函數(shù)調(diào)用棧如下,詳細(xì)請(qǐng)參閱《iOS - 老生常談內(nèi)存管理(四):內(nèi)存管理方法源碼分析》变逃。
// NSObject.mm
① objc_autorelease
// objc-object.h
② objc_object::autorelease
// NSObject.mm
③ autorelease
④ _objc_rootAutorelease
// objc-object.h
⑤ objc_object::rootAutorelease
// NSObject.mm
⑥ objc_object::rootAutorelease2
⑦ AutoreleasePoolPage::autorelease
AutoreleasePoolPage
類(lèi)的autorelease
方法實(shí)現(xiàn)如下:
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;
}
可以看到焚鹊,調(diào)用了autorelease
方法的對(duì)象,也是通過(guò)以上解析的autoreleaseFast()
方法添加進(jìn)Page
中韧献。
pop
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// 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 (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
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();
}
}
}
pop()
方法的傳參token
即為POOL_BOUNDARY
對(duì)應(yīng)在Page
中的地址末患。當(dāng)銷(xiāo)毀自動(dòng)釋放池時(shí),會(huì)調(diào)用pop()
方法將自動(dòng)釋放池中的autorelease
對(duì)象全部釋放(實(shí)際上是從自動(dòng)釋放池的中的最后一個(gè)入棧的autorelease
對(duì)象開(kāi)始锤窑,依次給它們發(fā)送一條release
消息璧针,直到遇到這個(gè)POOL_BOUNDARY
)。pop()
方法的執(zhí)行過(guò)程如下:
- ① 判斷
token
是不是EMPTY_POOL_PLACEHOLDER
渊啰,是的話就清空這個(gè)自動(dòng)釋放池探橱; - ② 如果不是的話,就通過(guò)
pageForPointer(token)
拿到token
所在的Page
(自動(dòng)釋放池的首個(gè)Page
)绘证; - ③ 通過(guò)
page->releaseUntil(stop)
將自動(dòng)釋放池中的autorelease
對(duì)象全部釋放隧膏,傳參stop
即為POOL_BOUNDARY
的地址; - ④ 判斷當(dāng)前
Page
是否有子Page
嚷那,有的話就銷(xiāo)毀胞枕。
pop()
方法中釋放autorelease
對(duì)象的過(guò)程在releaseUntil()
方法中,下面來(lái)看一下這個(gè)方法的實(shí)現(xiàn):
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next; // next指針是指向最后一個(gè)對(duì)象的后一個(gè)位置魏宽,所以需要先減1
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
releaseUntil()
方法其實(shí)就是通過(guò)一個(gè)while
循環(huán)腐泻,從最后一個(gè)入棧的autorelease
對(duì)象開(kāi)始,依次給它們發(fā)送一條release
消息队询,直到遇到這個(gè)POOL_BOUNDARY
派桩。
AutoreleasePoolPage()
我們來(lái)看一下創(chuàng)建一個(gè)Page
的過(guò)程。AutoreleasePoolPage()
方法的參數(shù)為parentPage
蚌斩,新創(chuàng)建的Page
的depth
加 1铆惑,next
指針的初始位置指向begin
,將新創(chuàng)建的Page
的parent
指針指向parentPage
送膳。將parentPage
的child
指針指向自己员魏,這就形成了雙向鏈表
的結(jié)構(gòu)。
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0)
{
if (parent) {
parent->check();
assert(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
begin肠缨、end逆趋、empty、full
下面再來(lái)看一下begin
晒奕、end
闻书、empty
名斟、full
這些方法的實(shí)現(xiàn)。
-
begin
的地址為:Page
自己的地址+Page
對(duì)象的大小56
個(gè)字節(jié)魄眉; -
end
的地址為:Page
自己的地址+4096
個(gè)字節(jié)砰盐; -
empty
判斷Page
是否為空的條件是next
地址是不是等于begin
; -
full
判斷Page
是否已滿的條件是next
地址是不是等于end
(棧頂)坑律。
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
小結(jié):
push
操作是往自動(dòng)釋放池中添加一個(gè)POOL_BOUNDARY
岩梳,并返回它存放的內(nèi)存地址;- 接著每有一個(gè)對(duì)象調(diào)用
autorelease
方法晃择,會(huì)將它的內(nèi)存地址添加進(jìn)自動(dòng)釋放池中冀值。pop
操作是傳入一個(gè)POOL_BOUNDARY
的內(nèi)存地址,從最后一個(gè)入棧的autorelease
對(duì)象開(kāi)始宫屠,將自動(dòng)釋放池中的autorelease
對(duì)象全部釋放(實(shí)際上是給它們發(fā)送一條release
消息)列疗,直到遇到這個(gè)POOL_BOUNDARY
。
4. 查看自動(dòng)釋放池的情況
可以通過(guò)以下私有函數(shù)來(lái)查看自動(dòng)釋放池的情況:
extern void _objc_autoreleasePoolPrint(void);
5. 使用 macOS 工程示例分析
由于iOS
工程中浪蹂,系統(tǒng)在自動(dòng)釋放池中注冊(cè)了一些對(duì)象抵栈。為了排除這些干擾,接下來(lái)我們通過(guò)macOS
工程代碼示例坤次,結(jié)合AutoreleasePoolPage
的內(nèi)存分布圖以及_objc_autoreleasePoolPrint()
私有函數(shù)古劲,來(lái)幫助我們更好地理解@autoreleasepool
的原理。
注意:
- 由于
ARC
環(huán)境下不能調(diào)用autorelease
等方法缰猴,所以需要將工程切換為MRC
環(huán)境产艾。- 如果使用
ARC
,則可以使用__autoreleasing
所有權(quán)修飾符替代autorelease
方法洛波。
單個(gè) @autoreleasepool
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool {
_objc_autoreleasePoolPrint(); // print2
HTPerson *p1 = [[[HTPerson alloc] init] autorelease];
HTPerson *p2 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
}
_objc_autoreleasePoolPrint(); // print4
return 0;
}
// 自動(dòng)釋放池的情況
objc[68122]: ############## (print1)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 0 releases pending. //當(dāng)前自動(dòng)釋放池中沒(méi)有任何對(duì)象
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: ##############
objc[68122]: ############## (print2)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 1 releases pending. //當(dāng)前自動(dòng)釋放池中有1個(gè)對(duì)象胰舆,這個(gè)對(duì)象為POOL_BOUNDARY
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68122]: ##############
objc[68122]: ############## (print3)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 3 releases pending. //當(dāng)前自動(dòng)釋放池中有3個(gè)對(duì)象
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68122]: [0x102802040] 0x100704a10 HTPerson //p1
objc[68122]: [0x102802048] 0x10075cc30 HTPerson //p2
objc[68122]: ##############
objc[68156]: ############## (print4)
objc[68156]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68156]: 0 releases pending. //當(dāng)前自動(dòng)釋放池中沒(méi)有任何對(duì)象,因?yàn)锧autoreleasepool作用域結(jié)束蹬挤,調(diào)用pop方法釋放了對(duì)象
objc[68156]: [0x100810000] ................ PAGE (hot) (cold)
objc[68156]: ##############
嵌套 @autoreleasepool
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool { //r1 = push()
_objc_autoreleasePoolPrint(); // print2
HTPerson *p1 = [[[HTPerson alloc] init] autorelease];
HTPerson *p2 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
@autoreleasepool { //r2 = push()
HTPerson *p3 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print4
@autoreleasepool { //r3 = push()
HTPerson *p4 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print5
} //pop(r3)
_objc_autoreleasePoolPrint(); // print6
} //pop(r2)
_objc_autoreleasePoolPrint(); // print7
} //pop(r1)
_objc_autoreleasePoolPrint(); // print8
return 0;
}
// 自動(dòng)釋放池的情況
objc[68285]: ############## (print1)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 0 releases pending. //當(dāng)前自動(dòng)釋放池中沒(méi)有任何對(duì)象
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: ##############
objc[68285]: ############## (print2)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 1 releases pending. //當(dāng)前自動(dòng)釋放池中有1個(gè)對(duì)象
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: ##############
objc[68285]: ############## (print3)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 3 releases pending. //當(dāng)前自動(dòng)釋放池中有3個(gè)對(duì)象(1個(gè)@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: ##############
objc[68285]: ############## (print4)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 5 releases pending. //當(dāng)前自動(dòng)釋放池中有5個(gè)對(duì)象(2個(gè)@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: [0x102802050] ################ POOL 0x102802050 //POOL_BOUNDARY
objc[68285]: [0x102802058] 0x1005065b0 HTPerson //p3
objc[68285]: ##############
objc[68285]: ############## (print5)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 7 releases pending. //當(dāng)前自動(dòng)釋放池中有7個(gè)對(duì)象(3個(gè)@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: [0x102802050] ################ POOL 0x102802050 //POOL_BOUNDARY
objc[68285]: [0x102802058] 0x1005065b0 HTPerson //p3
objc[68285]: [0x102802060] ################ POOL 0x102802060 //POOL_BOUNDARY
objc[68285]: [0x102802068] 0x100551880 HTPerson //p4
objc[68285]: ##############
objc[68285]: ############## (print6)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 5 releases pending. //當(dāng)前自動(dòng)釋放池中有5個(gè)對(duì)象(第3個(gè)@autoreleasepool已釋放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038
objc[68285]: [0x102802040] 0x100707d80 HTPerson
objc[68285]: [0x102802048] 0x100707de0 HTPerson
objc[68285]: [0x102802050] ################ POOL 0x102802050
objc[68285]: [0x102802058] 0x1005065b0 HTPerson
objc[68285]: ##############
objc[68285]: ############## (print7)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 3 releases pending. //當(dāng)前自動(dòng)釋放池中有3個(gè)對(duì)象(第2、3個(gè)@autoreleasepool已釋放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038
objc[68285]: [0x102802040] 0x100707d80 HTPerson
objc[68285]: [0x102802048] 0x100707de0 HTPerson
objc[68285]: ##############
objc[68285]: ############## (print8)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 0 releases pending. //當(dāng)前自動(dòng)釋放池沒(méi)有任何對(duì)象(3個(gè)@autoreleasepool都已釋放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: ##############
復(fù)雜情況 @autoreleasepool
由AutoreleasePoolPage
類(lèi)的定義可知棘幸,自動(dòng)釋放池(即所有的AutoreleasePoolPage
對(duì)象)是以棧
為結(jié)點(diǎn)通過(guò)雙向鏈表
的形式組合而成焰扳。每當(dāng)Page
滿了的時(shí)候,就會(huì)創(chuàng)建一個(gè)新的Page
误续,并設(shè)置它為hotPage
吨悍,而首個(gè)Page
為coldPage
。接下來(lái)我們來(lái)看一下多個(gè)Page
和多個(gè)@autoreleasepool
嵌套的情況蹋嵌。
int main(int argc, const char * argv[]) {
@autoreleasepool { //r1 = push()
for (int i = 0; i < 600; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
@autoreleasepool { //r2 = push()
for (int i = 0; i < 500; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
@autoreleasepool { //r3 = push()
for (int i = 0; i < 200; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
} //pop(r3)
} //pop(r2)
} //pop(r1)
return 0;
}
一個(gè)AutoreleasePoolPage
對(duì)象的內(nèi)存大小為4096
個(gè)字節(jié)育瓜,它自身成員變量占用內(nèi)存56
個(gè)字節(jié),所以剩下的4040
個(gè)字節(jié)用來(lái)存儲(chǔ)autorelease
對(duì)象的內(nèi)存地址栽烂。又因?yàn)?code>64bit 系統(tǒng)下一個(gè)OC
對(duì)象的指針?biāo)純?nèi)存為8
個(gè)字節(jié)躏仇,所以一個(gè)Page
可以存放505
個(gè)對(duì)象的地址恋脚。POOL_BOUNDARY
也是一個(gè)對(duì)象,因?yàn)樗闹禐?code>nil焰手。所以以上代碼的自動(dòng)釋放池內(nèi)存分布圖如下所示糟描。
objc[69731]: ##############
objc[69731]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[69731]: 1303 releases pending. //當(dāng)前自動(dòng)釋放池中有1303個(gè)對(duì)象(3個(gè)POOL_BOUNDARY和1300個(gè)HTPerson實(shí)例)
objc[69731]: [0x100806000] ................ PAGE (full) (cold) /* 第一個(gè)PAGE,full代表已滿书妻,cold代表coldPage */
objc[69731]: [0x100806038] ################ POOL 0x100806038 //POOL_BOUNDARY
objc[69731]: [0x100806040] 0x10182a040 HTPerson //p1
objc[69731]: [0x100806048] ..................... //...
objc[69731]: [0x100806ff8] 0x101824e40 HTPerson //p504
objc[69731]: [0x102806000] ................ PAGE (full) /* 第二個(gè)PAGE */
objc[69731]: [0x102806038] 0x101824e50 HTPerson //p505
objc[69731]: [0x102806040] ..................... //...
objc[69731]: [0x102806330] 0x101825440 HTPerson //p600
objc[69731]: [0x102806338] ################ POOL 0x102806338 //POOL_BOUNDARY
objc[69731]: [0x102806340] 0x101825450 HTPerson //p601
objc[69731]: [0x102806348] ..................... //...
objc[69731]: [0x1028067e0] 0x101825d90 HTPerson //p1008
objc[69731]: [0x102804000] ................ PAGE (hot) /* 第三個(gè)PAGE船响,hot代表hotPage */
objc[69731]: [0x102804038] 0x101826dd0 HTPerson //p1009
objc[69731]: [0x102804040] ..................... //...
objc[69731]: [0x102804310] 0x101827380 HTPerson //p1100
objc[69731]: [0x102804318] ################ POOL 0x102804318 //POOL_BOUNDARY
objc[69731]: [0x102804320] 0x101827390 HTPerson //p1101
objc[69731]: [0x102804328] ..................... //...
objc[69731]: [0x102804958] 0x10182b160 HTPerson //p1300
objc[69731]: ##############
6. 使用 iOS 工程示例分析
從以上macOS
工程示例可以得知,在@autoreleasepool
大括號(hào)結(jié)束的時(shí)候躲履,就會(huì)調(diào)用Page
的pop()
方法见间,給@autoreleasepool
中的autorelease
對(duì)象發(fā)送release
消息。
那么在iOS
工程中工猜,方法里的autorelease
對(duì)象是什么時(shí)候釋放的呢米诉?有系統(tǒng)干預(yù)釋放和手動(dòng)干預(yù)釋放兩種情況。
- 系統(tǒng)干預(yù)釋放是不指定
@autoreleasepool
域慷,所有autorelease
對(duì)象都由主線程的RunLoop
創(chuàng)建的@autoreleasepool
來(lái)管理荒辕。 - 手動(dòng)干預(yù)釋放就是將
autorelease
對(duì)象添加進(jìn)我們手動(dòng)創(chuàng)建的@autoreleasepool
中。
下面還是在MRC
環(huán)境下進(jìn)行分析犹褒。
系統(tǒng)干預(yù)釋放
我們先來(lái)看以下 Xcode 11 版本的iOS
程序中的main()
函數(shù)抵窒,和舊版本的差異。
// Xcode 11
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
// Xcode 舊版本
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
注意:
網(wǎng)上對(duì)于iOS
工程的main()
函數(shù)中的@autoreleasepool
有一種解釋?zhuān)?/strong>
在iOS
工程的main()
函數(shù)中有一個(gè)@autoreleasepool
,這個(gè)@autoreleasepool
負(fù)責(zé)了應(yīng)用程序所有autorelease
對(duì)象的釋放敛摘。
其實(shí)這個(gè)解釋是錯(cuò)誤的温学。
如果你的程序使用了AppKit
或UIKit
框架,那么主線程的RunLoop
就會(huì)在每次事件循環(huán)迭代中創(chuàng)建并處理@autoreleasepool
掉房。也就是說(shuō),應(yīng)用程序所有autorelease
對(duì)象的都是由RunLoop
創(chuàng)建的@autoreleasepool
來(lái)管理慰丛。而main()
函數(shù)中的@autoreleasepool
只是負(fù)責(zé)管理它的作用域中的autorelease
對(duì)象卓囚。
在以上《使用 MacOS 工程示例分析》
章節(jié)中提到了嵌套@autoreleasepool
的情況。Xcode 舊版本的main
函數(shù)中是將整個(gè)應(yīng)用程序運(yùn)行(UIApplicationMain
)放在@autoreleasepool
內(nèi)诅病,而主線程的RunLoop
就是在UIApplicationMain
中創(chuàng)建哪亿,所以RunLoop
創(chuàng)建的@autoreleasepool
是嵌套在main
函數(shù)的@autoreleasepool
內(nèi)的。RunLoop
會(huì)在每次事件循環(huán)中對(duì)自動(dòng)釋放池進(jìn)行pop
和push
(以下會(huì)詳細(xì)講解)贤笆,但是它的pop
只會(huì)釋放掉它的POOL_BOUNDARY
之后的對(duì)象蝇棉,它并不會(huì)影響到外層即main
函數(shù)中@autoreleasepool
。
新版本 Xcode 11 中的 main 函數(shù)發(fā)生了哪些變化芥永?
舊版本是將整個(gè)應(yīng)用程序運(yùn)行放在@autoreleasepool
內(nèi)篡殷,由于RunLoop
的存在,要return
即程序結(jié)束后@autoreleasepool
作用域才會(huì)結(jié)束埋涧,這意味著程序結(jié)束后main
函數(shù)中的@autoreleasepool
中的autorelease
對(duì)象才會(huì)釋放板辽。
而在 Xcode 11中奇瘦,觸發(fā)主線程RunLoop
的UIApplicationMain
函數(shù)放在了@autoreleasepool
外面,這可以保證@autoreleasepool
中的autorelease
對(duì)象在程序啟動(dòng)后立即釋放戳气。正如新版本的@autoreleasepool
中的注釋所寫(xiě) “Setup code that might create autoreleased objects goes here.
”(如上代碼)链患,可以將autorelease
對(duì)象放在此處。
接著我們來(lái)看 “系統(tǒng)干預(yù)釋放” 情況的示例:
- (void)viewDidLoad {
[super viewDidLoad];
HTPerson *person = [[[HTPerson alloc] init] autorelease];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[HTPerson dealloc]
// -[ViewController viewDidAppear:]
可以看到瓶您,調(diào)用了autorelease
方法的person
對(duì)象不是在viewDidLoad
方法結(jié)束后釋放麻捻,而是在viewWillAppear
方法結(jié)束后釋放,說(shuō)明在viewWillAppear
方法結(jié)束的時(shí)候呀袱,調(diào)用了pop()
方法釋放了person
對(duì)象贸毕。其實(shí)這是由RunLoop
控制的,下面來(lái)講解一下RunLoop
和@autoreleasepool
的關(guān)系夜赵。
RunLoop 與 @autoreleasepool
學(xué)習(xí)這個(gè)知識(shí)點(diǎn)之前明棍,需要先搞懂
RunLoop
的事件循環(huán)機(jī)制以及它的6
種活動(dòng)狀態(tài),可以參閱:
《深入淺出 RunLoop(二):數(shù)據(jù)結(jié)構(gòu)》
《深入淺出 RunLoop(三):事件循環(huán)機(jī)制》
iOS
在主線程的RunLoop
中注冊(cè)了兩個(gè)Observer
寇僧。
- 第 1 個(gè)
Observer
監(jiān)聽(tīng)了kCFRunLoopEntry
事件摊腋,會(huì)調(diào)用objc_autoreleasePoolPush()
; - 第 2 個(gè)
Observer
① 監(jiān)聽(tīng)了kCFRunLoopBeforeWaiting
事件嘁傀,會(huì)調(diào)用objc_autoreleasePoolPop()
兴蒸、objc_autoreleasePoolPush()
;
② 監(jiān)聽(tīng)了kCFRunLoopBeforeExit
事件细办,會(huì)調(diào)用objc_autoreleasePoolPop()
橙凳。
所以,在iOS
工程中系統(tǒng)干預(yù)釋放的autorelease
對(duì)象的釋放時(shí)機(jī)是由RunLoop
控制的笑撞,會(huì)在當(dāng)前RunLoop
每次循環(huán)結(jié)束時(shí)釋放岛啸。以上person
對(duì)象在viewWillAppear
方法結(jié)束后釋放,說(shuō)明viewDidLoad
和viewWillAppear
方法在同一次循環(huán)里茴肥。
-
kCFRunLoopEntry
:在即將進(jìn)入RunLoop
時(shí)坚踩,會(huì)自動(dòng)創(chuàng)建一個(gè)__AtAutoreleasePool
結(jié)構(gòu)體對(duì)象,并調(diào)用objc_autoreleasePoolPush()
函數(shù)瓤狐。 -
kCFRunLoopBeforeWaiting
:在RunLoop
即將休眠時(shí)堕虹,會(huì)自動(dòng)銷(xiāo)毀一個(gè)__AtAutoreleasePool
對(duì)象,調(diào)用objc_autoreleasePoolPop()
芬首。然后創(chuàng)建一個(gè)新的__AtAutoreleasePool
對(duì)象,并調(diào)用objc_autoreleasePoolPush()
逼裆。 -
kCFRunLoopBeforeExit
郁稍,在即將退出RunLoop
時(shí),會(huì)自動(dòng)銷(xiāo)毀最后一個(gè)創(chuàng)建的__AtAutoreleasePool
對(duì)象胜宇,并調(diào)用objc_autoreleasePoolPop()
耀怜。
手動(dòng)干預(yù)釋放
我們?cè)賮?lái)看一下手動(dòng)干預(yù)釋放的情況恢着。
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
HTPerson *person = [[[HTPerson alloc] init] autorelease];
}
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
// -[HTPerson dealloc]
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[ViewController viewDidAppear:]
可以看到,添加進(jìn)手動(dòng)指定的@autoreleasepool
中的autorelease
對(duì)象财破,在@autoreleasepool
大括號(hào)結(jié)束時(shí)就會(huì)釋放掰派,不受RunLoop
控制。
相關(guān)問(wèn)題
Q:ARC 環(huán)境下左痢,autorelease 對(duì)象在什么時(shí)候釋放靡羡?
回到我們最初的面試題,在ARC
環(huán)境下俊性,autorelease
對(duì)象在什么時(shí)候釋放略步?我們就分系統(tǒng)干預(yù)釋放
和手動(dòng)干預(yù)釋放
兩種情況回答。
Q:ARC 環(huán)境下定页,需不需要手動(dòng)添加 @autoreleasepool趟薄?
AppKit 和 UIKit 框架會(huì)在RunLoop
每次事件循環(huán)迭代中創(chuàng)建并處理@autoreleasepool
,因此典徊,你通常不必自己創(chuàng)建@autoreleasepool
杭煎,甚至不需要知道創(chuàng)建@autoreleasepool
的代碼怎么寫(xiě)。但是卒落,有些情況需要自己創(chuàng)建@autoreleasepool
羡铲。
例如,如果我們需要在循環(huán)中創(chuàng)建了很多臨時(shí)的autorelease
對(duì)象导绷,則手動(dòng)添加@autoreleasepool
來(lái)管理這些對(duì)象可以很大程度地減少內(nèi)存峰值犀勒。比如在for
循環(huán)中alloc
圖片數(shù)據(jù)等內(nèi)存消耗較大的場(chǎng)景,需要手動(dòng)添加@autoreleasepool
妥曲。
蘋(píng)果給出了三種需要手動(dòng)添加
@autoreleasepool
的情況:
- ① 如果你編寫(xiě)的程序不是基于 UI 框架的贾费,比如說(shuō)命令行工具;
- ② 如果你編寫(xiě)的循環(huán)中創(chuàng)建了大量的臨時(shí)對(duì)象檐盟;
你可以在循環(huán)內(nèi)使用@autoreleasepool
在下一次迭代之前處理這些對(duì)象褂萧。在循環(huán)中使用@autoreleasepool
有助于減少應(yīng)用程序的最大內(nèi)存占用。- ③ 如果你創(chuàng)建了輔助線程葵萎。
一旦線程開(kāi)始執(zhí)行导犹,就必須創(chuàng)建自己的@autoreleasepool
;否則羡忘,你的應(yīng)用程序?qū)⒋嬖趦?nèi)存泄漏谎痢。
版本更新
版本日期 | 更新內(nèi)容 |
---|---|
2020.3.17 | 發(fā)布文章 |
2020.4.17 | 1.【Feature】對(duì)釋放 NSAutoreleasePool 對(duì)象,使用 [pool release] 與 [pool drain] 的區(qū)別卷雕? 問(wèn)題的解釋?zhuān)?br>2.【Update】錯(cuò)誤的解釋在 iOS 中 main 函數(shù)中的 @autoreleasepool 負(fù)責(zé)整個(gè)應(yīng)用程序 autorelease 對(duì)象的釋放 节猿;3.【Feature】對(duì) 新版本 Xcode 11 中的 main 函數(shù)發(fā)生了哪些變化? 問(wèn)題的解釋?zhuān)?br>4.【Feature】源碼分析,Runtime 最新源代碼objc4-779.1 中對(duì)AutoreleasePoolPage 類(lèi)的更新:AutoreleasePoolPage 現(xiàn)在繼承自AutoreleasePoolPageData 滨嘱。 |
我的博客即將同步至騰訊云+社區(qū)峰鄙,邀請(qǐng)大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1g6tbyhm63kr3