一、介紹
autoreleasepool 自動(dòng)釋放池梢杭,在池子里的對(duì)象如果沒有被強(qiáng)引用都會(huì)自動(dòng)釋放掉温兼,自動(dòng)釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage武契,調(diào)用了autorelease的對(duì)象最終都是通過 AutoreleasePoolPage 對(duì)象來管理的募判。
二缸榛、源碼分析 - clang重寫 @autoreleasepool
先關(guān)閉ARC,Build Setting 搜索 Objective-C Automatic Reference Counting Yes 改為 No兰伤,使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp 命令生成C++代碼内颗。
main.m代碼如下:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person = [[[Person alloc]init]autorelease];
}
return 0;
}
main.cpp對(duì)應(yīng)代碼如下:
int main(int argc, char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
return 0;
}
__AtAutoreleasePool 結(jié)構(gòu)體對(duì)應(yīng)的代碼如下:
/// struct 有點(diǎn)類似 Class
struct __AtAutoreleasePool {
/// 構(gòu)造函數(shù) 創(chuàng)建對(duì)象
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
/// 析構(gòu)函數(shù) 銷毀對(duì)象
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
/// 聲明的對(duì)象(接收構(gòu)造函數(shù)生成的對(duì)象,也是傳入析構(gòu)函數(shù)的)
void * atautoreleasepoolobj;
};
一個(gè)@autoreleasepool的 { 和 } 相當(dāng)于創(chuàng)建和銷毀敦腔,整個(gè)作用域就在大括號(hào)內(nèi)均澳,那么其實(shí)開始的main.m里面的代碼相當(dāng)于如下代碼:
int main(int argc, char * argv[]) {
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc]init]autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
return 0;
}
三、源碼分析 AutoreleasePoolPage類對(duì)象
想要知道objc_autoreleasePoolPush和Pop做了什么符衔,那就得去看看objc4源碼里面具體做了啥找前,可以看到Push是調(diào)用了 AutoreleasePoolPage 類的 push() 方法,反之就是 pop()方法判族,所以自動(dòng)釋放池就是 AutoreleasePoolPage 對(duì)象來管理的躺盛。
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage 類大概有600多行代碼,截取部分代碼展示如下:
class AutoreleasePoolPage
{
magic_t const magic;
/// 指向下一個(gè)可放對(duì)象的地址
id *next;
/// 在固定的一個(gè)線程執(zhí)行形帮,線程一一對(duì)應(yīng)槽惫,線程之間的釋放池是分開的
pthread_t const thread;
/// 鏈表父指針,指向上一個(gè)Page對(duì)象
AutoreleasePoolPage * const parent;
/// 鏈表子指針辩撑,指向下一個(gè)Page對(duì)象
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
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();
}
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
/// 以前這個(gè)SIZE = PAGE_MAX_SIZE = PAGE_SIZE = 4096
/// 現(xiàn)在好像是SIZE = PAGE_MAX_SIZE = 1 << 14界斜,就是二進(jìn)制的100000000000000 十進(jìn)制的1乘以2的14次方,即2的14次方 = 16384合冀,還是說我下載的[obj4](https://opensource.apple.com/tarballs/objc4/)代碼版本不對(duì)各薇,望知道的大佬告訴我一下
return (id *) ((uint8_t *)this+SIZE);
}
~AutoreleasePoolPage()
{
check();
unprotect();
assert(empty());
assert(!child);
}
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
void releaseAll()
{
releaseUntil(begin());
}
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;
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
}
void kill()
{
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
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;
}
/// 方法實(shí)現(xiàn)省略
static inline void *push() {}
static inline void pop(void *token) {}
static void init() {}
void print() {}
static void printAll() {}
static void printHiwat()
};
上面有個(gè) end() 方法可以知道每個(gè) AutoreleasePoolPage 對(duì)象的大小,以前這個(gè)SIZE = PAGE_MAX_SIZE = PAGE_SIZE = 4096君躺,現(xiàn)在好像是SIZE = PAGE_MAX_SIZE = 1 << 14峭判,就是二進(jìn)制的100000000000000 十進(jìn)制的1乘以2的14次方,即2的14次方 = 16384棕叫,還是說我下載的obj4代碼版本不對(duì)林螃,望知道的大佬告訴我一下,以下就先按照4096來講谍珊。
每個(gè) AutoreleasePoolPage 對(duì)象占用4096字節(jié)內(nèi)存治宣,除了用來存放它內(nèi)部的成員變量急侥,剩下的空間用來存放 autorelease 對(duì)象(調(diào)用了autorelease方法的對(duì)象砌滞,如上面的person對(duì)象)的地址所有的 AutoreleasePoolPage 對(duì)象通過雙向鏈表的形式連接在一起,第一個(gè)對(duì)象的parent指向的是空坏怪,child指向下一個(gè)對(duì)象贝润,下一個(gè)對(duì)象的parent指向上一個(gè)對(duì)象,child指向下一個(gè)對(duì)象铝宵,這樣循環(huán)最后一個(gè)對(duì)象的child指向是空打掘,這就是雙向鏈表結(jié)構(gòu)华畏。
假設(shè)第一個(gè) AutoreleasePoolPage 對(duì)象的起始地址是 0x1000,那么結(jié)束地址就是 0x2000尊蚁,16進(jìn)制換算一下 0x1000 = 4096亡笑,自己所帶的7個(gè)成員變量占56個(gè)字節(jié),所以能夠存放 autorelease 對(duì)象的地址就是0x1038~0x2000横朋,也可以通過 begin()方法算出開始地址是多少仑乌。
四、 objc_autoreleasePoolPush琴锭,objc_autoreleasePoolPop晰甚,autorelease 方法做了什么
AutoreleasePoolPage 類對(duì)象中*next 指向了下一個(gè)能存放 autorelease 對(duì)象地址的區(qū)域 。
調(diào)用 objc_autoreleasePoolPush 方法會(huì)將一個(gè) POOL_BOUNDARY 入棧(數(shù)據(jù)結(jié)構(gòu)算是棧結(jié)構(gòu))决帖,并且返回其存放的內(nèi)存地址厕九。
調(diào)用 objc_autoreleasePoolPop 方法時(shí)傳入一個(gè) POOL_BOUNDARY 的內(nèi)存地址,會(huì)從最后一個(gè)入棧的對(duì)象調(diào)用對(duì)象的 release 方法地回,直到遇到這個(gè) POOL_BOUNDARY扁远,可以在上面的部分源碼 releaseAll()方法里面查看到。
對(duì)象的 autorelease 方法 通過斷點(diǎn)看匯編代碼發(fā)現(xiàn)方法實(shí)際是調(diào)用了 objc_autorelease() 方法刻像,把對(duì)象添加到Page對(duì)象中去穿香,通過objc4源碼可以看到調(diào)用順序如下。
1绎速、obj->autorelease()
2皮获、rootAutorelease()
3、rootAutorelease2()
4纹冤、AutoreleasePoolPage類的autorelease()
5洒宝、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);
}
}
objc_autoreleasePoolPush,objc_autoreleasePoolPop
int main(int argc, char * argv[]) {
@autoreleasepool {
for (int i = 0; i<1000; i++) {
Person *person = [[[Person alloc]init]autorelease];
}
}
/**上面代碼 翻譯過來基本就是下面這樣*/
/// 將 POOL_BOUNDARY 入棧 返回地址其實(shí)就是 0x1038 因?yàn)榈谝粋€(gè)可存放對(duì)象的地址就是它
atautoreleasepoolobj = objc_autoreleasePoolPush();
// atautoreleasepoolobj = 0x1038;
/// 循環(huán)將 person 對(duì)象放入到 AutoreleasePoolPage 對(duì)象去萌京,不夠就新建一個(gè)Page對(duì)象child指針指向
for (int i = 0; i<1000; i++) {
Person *person = [[[Person alloc]init]autorelease];
}
/// 將 0x1038 傳入雁歌,從棧頂開始釋放對(duì)象,一直釋放到 0x1038 對(duì)象為止
objc_autoreleasePoolPop(atautoreleasepoolobj);
// objc_autoreleasePoolPop(0x1038);
return 0;
}
如果是 autoreleasepool 嵌套知残,其實(shí)也是一樣的靠瞎,沒有用完一個(gè)page對(duì)象的空間不會(huì)再生成一個(gè)新的page對(duì)象,而是在原對(duì)象中繼續(xù)壓入POOL_BOUNDARY來區(qū)分求妹。
int main(int argc, char * argv[]) {
/// 創(chuàng)建一個(gè) AutoreleasePoolPage 對(duì)象
@autoreleasepool { /// pool_boundary_1 = objc_autoreleasePoolPush(), POOL_BOUNDARY 壓進(jìn)去
/// person1 對(duì)象 壓入
Person *person1 = [[[Person alloc]init]autorelease];
/// person2 對(duì)象 壓入
Person *person2 = [[[Person alloc]init]autorelease];
/// 沒有超出 4096 字節(jié)乏盐,不用再創(chuàng)建 AutoreleasePoolPage 對(duì)象
@autoreleasepool { /// pool_boundary_2 = objc_autoreleasePoolPush(), POOL_BOUNDARY 再壓進(jìn)去
/// person3 對(duì)象 壓入
Person *person3 = [[[Person alloc]init]autorelease];
/// person4 對(duì)象 壓入
Person *person4 = [[[Person alloc]init]autorelease];
} /// objc_autoreleasePoolPop(pool_boundary_2), 從棧頂開始釋放到 pool_boundary_2 為止
/// person5 對(duì)象 壓入
Person *person5 = [[[Person alloc]init]autorelease];
/// person6 對(duì)象 壓入
Person *person6 = [[[Person alloc]init]autorelease];
} /// objc_autoreleasePoolPop(pool_boundary_1), 從棧頂開始釋放到 pool_boundary_1 為止
return 0;
}
五、通過 _objc_autoreleasePoolPrint 方法查看自動(dòng)釋放池的具體情況
在 objc4 源碼的 NSObject.mm 文件中有一個(gè) _objc_autoreleasePoolPrint 方法制恍,實(shí)際調(diào)用了 AutoreleasePoolPage的打印輸出方法父能,我們來看看能看到哪些信息。
void _objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
例子代碼如下
/// 需要先這樣定義一下方法名净神,這樣才能調(diào)用何吝,runtime才會(huì)去Foundation框架去找實(shí)現(xiàn)的方法
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person1 = [[[Person alloc]init]autorelease];
Person *person2 = [[[Person alloc]init]autorelease];
_objc_autoreleasePoolPrint();
@autoreleasepool {
Person *person3 = [[[Person alloc]init]autorelease];
Person *person4 = [[[Person alloc]init]autorelease];
}
}
return 0;
}
打印如下溉委,
- 3 release pending 是因?yàn)榈谝粋€(gè)是Push壓入的 POOL_BOUNDARY 所以 POOL 0x7fe4e380e038 代表的就是,然后才是person1爱榕,person2對(duì)象瓣喊。
- PAGE (hot) (cold) 代表的就是一個(gè) AutoreleasePoolPage 對(duì)象,(hot) 代表的是當(dāng)前正在使用的
objc[10164]: ##############
objc[10164]: AUTORELEASE POOLS for thread 0x103d34600
objc[10164]: 3 releases pending.
objc[10164]: [0x7fe4e380e000] ................ PAGE (hot) (cold)
objc[10164]: [0x7fe4e380e038] ################ POOL 0x7fe4e380e038
objc[10164]: [0x7fe4e380e040] 0x6000020440c0 Person
objc[10164]: [0x7fe4e380e048] 0x6000020440d0 Person
objc[10164]: ##############
再換一個(gè)例子
/// 需要先這樣定義一下方法名黔酥,這樣才能調(diào)用型宝,runtime才會(huì)去Foundation框架去找實(shí)現(xiàn)的方法
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person1 = [[[Person alloc]init]autorelease];
Person *person2 = [[[Person alloc]init]autorelease];
@autoreleasepool {
for (int i = 0; i<1000; i++) {
Person *person3 = [[[Person alloc]init]autorelease];
}
_objc_autoreleasePoolPrint();
}
}
return 0;
}
打印如下,第一個(gè) PAGE 多了一個(gè) (full)絮爷,代表第一個(gè)對(duì)象滿了趴酣,沒有了 (hot) 代表不是當(dāng)前使用頁,當(dāng)前使用頁切到下一個(gè) PAGE 對(duì)象上了坑夯。
objc[10354]: ##############
objc[10354]: AUTORELEASE POOLS for thread 0x110cf5600
objc[10354]: 1004 releases pending.
objc[10354]: [0x7fd8fb009000] ................ PAGE (full) (cold)
objc[10354]: [0x7fd8fb009038] ################ POOL 0x7fd8fb009038
objc[10354]: [0x7fd8fb009040] 0x600000f1c020 Person
objc[10354]: [0x7fd8fb009048] 0x600000f1c030 Person
objc[10354]: [0x7fd8fb009050] ################ POOL 0x7fd8fb009050
objc[10354]: [0x7fd8fb009058] 0x600000f1c040 Person
......
......
......
objc[10354]: [0x7fd8fb00b000] ................ PAGE (hot)
......
......
......
objc[10354]: [0x7fd8fb00bfc8] 0x600000f1feb0 Person
objc[10354]: ##############
六岖寞、Runloop與Autorelease的關(guān)系(對(duì)象到底在什么時(shí)候調(diào)用對(duì)象的release方法)
先來看一段代碼,MRC演示環(huán)境柜蜈。
1像街、第1個(gè) Observer 監(jiān)聽了 kCFRunLoopEntry 事件黎棠,會(huì)調(diào)用 objc_autoreleasePoolPush()
2、第2個(gè) Observer 監(jiān)聽了 kCFRunLoopBeforeWaiting 事件镰绎,會(huì)調(diào)用objc_autoreleasePoolPop()脓斩、objc_autoreleasePoolPush() ,監(jiān)聽了kCFRunLoopBeforeExit事件跟狱,會(huì)調(diào)用objc_autoreleasePoolPop()
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), /// 1
kCFRunLoopBeforeTimers = (1UL << 1), /// 2
kCFRunLoopBeforeSources = (1UL << 2),/// 4
kCFRunLoopBeforeWaiting = (1UL << 5),/// 32
kCFRunLoopAfterWaiting = (1UL << 6),/// 64
kCFRunLoopExit = (1UL << 7),/// 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
以前打印當(dāng)前RunLoop是可以看到注冊(cè)的2個(gè)Observer的俭厚,兩個(gè) callout = _wrapRunLoopWithAutoreleasePoolHandler ,activities = 0x1 = 1 = kCFRunLoopEntry 和 activities = 0xa0 = 160 = kCFRunLoopBeforeWaiting | kCFRunLoopExit驶臊,但是現(xiàn)在打印看不到了挪挤,知道原因的大佬可以留言告訴我一下,謝謝关翎!
結(jié)論:
- MRC時(shí)候?qū)ο笳{(diào)用 autorelease 方法加入到自動(dòng)釋放池后扛门,release 的時(shí)機(jī)是在所在的一次 RunLoop 結(jié)束的時(shí)候釋放。
-
ARC 時(shí)候由系統(tǒng)管理纵寝,系統(tǒng)沒有調(diào)用 autorelease 方法论寨,而是在合適的地方(離開對(duì)象作用域之前)底層調(diào)用了 person = nil 方法。
從匯編代碼看確實(shí)相當(dāng)于作用域離開前調(diào)用了 person = nil; 也就是 objc_storeStrong(&person,nil)
void objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
七爽茴、擴(kuò)展 Tagged Pointer
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;
}
有沒有發(fā)現(xiàn) obj->isTaggedPointer() 這里斷言了葬凳,如果是Tagged Pointer類型對(duì)象就不加入到自動(dòng)釋放池里去,因?yàn)檫@種類型數(shù)據(jù)是存在指針里面的室奏,指針本身也是有內(nèi)存地址的火焰,本身也能存儲(chǔ)少量的數(shù)據(jù)。Tagged Pointer 詳解
如有錯(cuò)誤請(qǐng)幫忙指出胧沫,謝謝昌简!轉(zhuǎn)載請(qǐng)注明出處,喜歡的話绒怨,請(qǐng)點(diǎn)個(gè)贊吧纯赎!