一伦籍、@autoreleasepool
到底是干什么的馁蒂?
使用clang -rewrite-objc main.m
將
@autoreleasepool {
}
翻譯成C++文件可知
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
return 0;
}
}
全局搜索可以得到
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;
};
看代碼可知其實是個結構體绘证,上面的代碼可以寫成
atautoreleasepoolobj = objc_autoreleasePoolPush()
...
objc_autoreleasePoolPop(atautoreleasepoolobj)
二、那objc_autoreleasePoolPush
和objc_autoreleasePoolPop
又是什么呢?
查看runtime的objc4-646目錄下的NSObject.mm
源代碼可以知道具體的實現(xiàn)
AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
- magic用來校驗AutoreleasePoolPage結構是否完整;
- next指向第一個可用的地址寓涨;
- thread指向當前的線程;
- parent指向父類
- child指向子類
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
這兩個方法快速獲得可用的地址范圍
每一個autoreleasepool
都是由一個或多個AutoreleasePoolPage
的雙向鏈表組成的
static size_t const SIZE PAGE_MAX_SIZE; // size and alignment, power of 2
static size_t const COUNT = SIZE / sizeof(id);
展開就是
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define PAGE_MAX_SIZE PAGE_SIZE
所以:AutoreleasePoolPage的大小都是一樣的4096
關于AutoreleasePoolPage的數(shù)據(jù)結構借用一下Draveness的圖
大致搞清楚了autoreleasepool
的結構我們再下面從autoreleasepool
的創(chuàng)建和對象如何加到autoreleasepool
來具體說說中間都發(fā)生了什么
三氯檐、autoreleasepool
的創(chuàng)建
當我們使用@autoreleasepool { }的時候戒良,由上面知道實際是先調用了objc_autoreleasePoolPush
方法,我們先看看objc_autoreleasePoolPush的實現(xiàn)
void *objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
展開其實就是
static inline void *push()
{
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
相信大家都看到了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);
}
}
這個方法就是AutoreleasePoolPage具體初始化的地方冠摄,看實現(xiàn)知道有三種情況
- hotPage存在并且容量沒有滿糯崎,直接添加對象
- hotPage存在但是容量已經(jīng)滿了,調用autoreleaseFullPage方法河泳,初始化一個AutoreleasePoolPage并把page傳入沃呢,并標記為hotPage;
- hotPage不存在拆挥,調用autoreleaseNoPage創(chuàng)建一個AutoreleasePoolPage薄霜,并標記為hotePage,并且添加一個POOL_SENTINEL(哨兵對象)
上面的autoreleaseFast方法就是創(chuàng)建autoreleasepool的核心實現(xiàn)
另外看到這個關鍵字了吧inline
纸兔,其實就是內聯(lián)函數(shù)惰瓜,可以提供執(zhí)行速度,可以看看我之前的文章
總結:每次push會產(chǎn)生一個新的autoreleasepool汉矿,并生成一個POOL_SENTINEL
四崎坊、對象是如何加到autoreleasepool中去的
說完autoreleasepool的創(chuàng)建,接下來說對象是如何加到autoreleasepool中去的洲拇,當對象調用【object autorelease】的方法的時候就會加到autoreleasepool中
+ (id)autorelease {
return (id)self;
}
// Replaced by ObjectAlloc
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
展開為
inline id
objc_object::rootAutorelease()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
if (fastAutoreleaseForReturn((id)this)) return (id)this;
return rootAutorelease2();
}
再展開
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
其實就是AutoreleasePoolPage的autorelease方法
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || *dest == obj);
return obj;
}
是不是覺得似曾相識奈揍?是的曲尸,和之前的push方法幾乎一樣,就是入?yún)⒉灰粯印?/p>
-
push
方法傳的是POOL_SENTINEL
-
autorelease
方法是傳的具體需要autoRelease對象
細心的同學肯定發(fā)現(xiàn)了assert(!obj->isTaggedPointer());
這個斷言里面的isTaggedPointer
了吧男翰,這個是蘋果后面為了提高內存利用率推出來的偽指針
另患,要想深入了解可以點擊此處。
對比完autoreleasepool
的創(chuàng)建和對象如何加到autoreleasepool
我們可以得出下面的結論:
- 只有push操作會產(chǎn)生
POOL_SENTINEL
奏篙; -
POOL_SENTINEL
的個數(shù)就是autoreleasepool
的個數(shù)柴淘,實際開發(fā)中會有嵌套使用的情況。
五秘通、說完添加接著來說說移除pop
當執(zhí)行到這個objc_autoreleasePoolPop
方法的時候
autoreleasepool
會向POOL_SENTINEL
地址后面的對象都發(fā)release
消息为严,直到第一個POOL_SENTINEL
對象截止。
我們來理解一下這個圖:
- 首先看到有兩個
POOL_SENTINEL
說明autoreleasepool
還嵌套了一個autoreleasepool
肺稀,當最新的autoreleasepool
執(zhí)行objc_autoreleasePoolPop
方法的時候第股,最右邊的AutoreleasePoolPage
向里面的對象發(fā)release
消息直到中間那個POOL_SENTINEL
為止 - 由
coldPage
方法的定義可知最左邊的那個是coldPage
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
依次類推。话原。夕吻。
六、實踐
理論說完了繁仁,究竟在什么地方用這個呢涉馅,看蘋果官方文檔說
- 如果你編寫的程序不是基于 UI 框架的,比如說命令行工具黄虱;
- 如果你編寫的循環(huán)中創(chuàng)建了大量的臨時對象稚矿;
- 如果你創(chuàng)建了一個輔助線程。
官方提供了一個例子
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
這里特地寫了一個demo測試這個
static const int kStep = 50000;
static const int kIterationCount = 10 * kStep;
//查看app運行內存
- (void)obserMemoryUsage{
NSNumber *num = nil;
NSString *str = nil;
for (int i = 0; i < kIterationCount; i++) {
@autoreleasepool {
num = [NSNumber numberWithInt:i];
str = [NSString stringWithFormat:@"打哈薩克的哈克實打實的哈克時間的話大聲疾呼多阿薩德愛仕達按時 "];
//Use num and str...whatever...
[NSString stringWithFormat:@"%@%@", num, str];
if (i % kStep == 0) {
double ff = getMemoryUsage();
NSLog(@"%f",ff);
}
}
}
}
double getMemoryUsage(void) {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
double memoryUsageInMB = kerr == KERN_SUCCESS ? (info.resident_size / 1024.0 / 1024.0) : 0.0;
return memoryUsageInMB;
}
統(tǒng)一操作了3次我們對比一下內存占用情況
加了 @autoreleasepool {}
控制臺
不加 @autoreleasepool {}
同樣是3次加了 @autoreleasepool {}內存穩(wěn)定在73左右捻浦,而不加 @autoreleasepool {}的會出現(xiàn)暴增的情況晤揣。
@autoreleasepool {}平時不怎么關心,仔細研究起來還是挺有用處的朱灿。
reference: