iOS autorelease 實現(xiàn)原理及釋放時機
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
person *p = [[person alloc] init];
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
我們見文件轉(zhuǎn)化為 C++ 文件,xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
,然后生成一個.cpp,
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
person *p = ((person *(*)(id, SEL))(void *)objc_msgSend)((id)((person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("person"), sel_registerName("alloc")), sel_registerName("init"));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
這個是我們生成的 main 的源碼,可以看到 在開頭生成了一個 __AtAutoreleasePool __autoreleasepool;
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
可以看到有構造函數(shù)和析構函數(shù),當我們創(chuàng)建結構體的時候,會調(diào)用構造函數(shù),當結構體銷毀的時候就會調(diào)用析構函數(shù)。所以但我們調(diào)用 __AtAutoreleasePool __autoreleasepool;
d的時候,就會調(diào)用我們的構造函數(shù),然后其實就是調(diào)用 objc_autoreleasePoolPush()
當我們除了作用于,結構體銷毀的時候,就會調(diào)用析構函數(shù),objc_autoreleasePoolPop(atautoreleasepoolobj);
就會調(diào)用這個函數(shù),一個 push 一個 pop.
所以我們的代碼就相當于
atautoreleasepoolobj = objc_autoreleasePoolPush();
person *p = [[person alloc] init];
objc_autoreleasePoolPop(atautoreleasepoolobj);
接下來結合源碼分析,其實我們的 autoreleasepool ,底層是靠一個叫做AutoreleasePoolPage
的數(shù)據(jù)結構管理的
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
每個 autoreleasepoolpage 占用4096 個字節(jié),除了用來存放他內(nèi)部的成員變量,剩下的用來存放autorelease對象的地址,所有的 autoreleasepoolpage是通過雙向鏈表連接起來的,child 會指向下一個autoreleasepoolpage,parent 指向上一個,每個 autoreleasepoolpage對象,里面存放著調(diào)用autorelease對象的地址值,存放著所有調(diào)用過的.
@autoreleasepool {
for (NSInteger i = 0 ; i < 1000; i ++) {
person *p = [[person alloc] init];
}
}
我們在 autorelpool里面調(diào)用了一千次,一個指針8個字節(jié),一共8000個,我們的autoreleasepoolpage,在存儲萬自己的實例變量一些信息過后可能剩余4000個字節(jié)來存儲對象的地址,那么就需要兩個autoreleasepoolpage來存儲。當我們調(diào)用pop的時候,就找到這1000個對象的地址值,然后釋放
當我們調(diào)用 push 的時候,會將 POOL_BOUNDARY 壓棧,其實內(nèi)部就是定義值為0,然后返回當前地址,當我們有對象調(diào)用 autorelease之后,就會在他的下面去壓棧,存儲調(diào)用autorelease對象的地址了,當我們調(diào)用pop的時候,我們就會從后往前,挨個調(diào)用release,一直到我們穿進去的那個 POOL_BOUNDARY 的位置,這樣就會將所有的autorelease兌現(xiàn)都釋放掉。其中next指針指向的是第一個能存放autorelease對象地址的位置,當我們調(diào)用一次push的時候,就會插入一個POOL_BOUNDARY,每次 pop 的時候,遇到 POOL_BOUNDARY 就會結束,釋放當前 autoreleasepoolpage 存儲的所有對象,每次pop的時候,會傳入POOL_BOUNDARY的地址,然后從最后一個入棧的autorelease對象開始釋放,如果一個page頁里面能存儲下,那么POOL_BOUNDARY會一直在當前page頁疊加,只有當前page存放不下,會新建一個page,然后入棧。
autorelease 什么時機會被釋放
runloop 注冊了兩個監(jiān)聽,我們打印下 mainrunloop
<CFRunLoop 0x600003194300 [0x10db64c30]>{wakeup port = 0x2303, stopped = false, ignoreWakeUps = false,
3 : <CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (
12 : <CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (
"<CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}",
"<CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}"
"<CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}",
"<CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}"
2020-03-01 16:03:18.346330+0800 testautoreelease[34048:1997377] persom dealloc
可以看到,runloop 注冊了監(jiān)聽,第一個observer監(jiān)聽了,KCFRunloopEntry事件,然后調(diào)用 objc_autoreleasePoolPush()
第二個observer 監(jiān)聽了,KCFRunloopBeforeWaiting 事件,然后調(diào)用objc_autoreleasePoolPop(),然后調(diào)用objc_autoreleasePoolPush()。
當我們runloop退出的時候,會調(diào)用一次 pop,和之前的 push 對上。
所以我們的 autorelease 是跟我們的runloop有關的,在我們這次runloop休眠之前會pop釋放。
所以如果我們生成的對象是autorelease的形式,那么不會立刻釋放,會等檔次的runloop結束之后釋放,但是我們發(fā)現(xiàn)我們在arc情況下,viewdidload 里面初始化一個,然后結束之后就會立刻釋放,說明很有可能編譯器為什么添加了release,當過了viewdidload作用于之后就會調(diào)用release將我們的對象釋放。