autorelease簡介:
autorelease是一種支持引用計數(shù)的內(nèi)存管理方式,只要給對象發(fā)送一條autorelease消息,會將對象放到一個自動釋放池中,當自動釋放池被銷毀時,會對池子里面的所有對象做一次release操作,
?使用autorelease有什么好處呢:不用再關(guān)心對象釋放的時間,不用再關(guān)心什么時候調(diào)用release
autorelesspool寫法:
1. NSAutoreleasePool*autoreleasePool = [[NSAutoreleasePoolalloc] init];
?Person *p = [[[Person alloc] init] autorelease];
?[autoreleasePool drain]; ?//池子銷毀會給池子中所有對象發(fā)送一條release消息,NSAutoreleasePool對象不能retain, 不能autorelease
2. @autoreleasepool{// 創(chuàng)建一個自動釋放池
Person *p = [[Personnew] autorelease];// 將代碼寫到這里就放入了自動釋放池
}// 銷毀自動釋放池(會給池子中所有對象發(fā)送一條release消息),在arc下自動管理。
MRC下需要對象調(diào)用autorelease才會入池, ARC下可以通過__autoreleasing修飾符, 否則的話看方法名, 非alloc/new/copy/mutableCopy開頭的初始化方法編譯器都會自動幫我們調(diào)用autorelease方法.
autorelease pool堆棧結(jié)構(gòu)
每一個autorelease pool創(chuàng)建都會將池子入棧,給池子發(fā)送drain或者release消息,該池子和其后創(chuàng)建的池子都將銷毀
例子:?
? ? NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc]init];
?? ? TestOne*one = [[[TestOnealloc]init]autorelease];
? ? NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc]init];
? ? TestTwo *two = [[[TestTwo alloc]init]autorelease];
? ? [pool1 drain];
? ? sleep(5); //阻礙pool的釋放,因為函數(shù)執(zhí)行完池子就會被銷毀,即使不發(fā)送release或drain消息
輸出:
TestTwo dealloc
TestOne dealloc
即使只使用了? [pool1 drain];one對象和two對象都能釋放
autorelease pool 與 線程
每一個線程(包括主線程)都有一個NSAutoreleasePool棧. 當一個新的池子被創(chuàng)建的時候, push進棧. 當池子被釋放內(nèi)存時, pop出棧. 對象調(diào)用autorelease方法進入棧頂?shù)某刈又? 當線程結(jié)束的時候, 它會自動地銷毀掉所有跟它有關(guān)聯(lián)的池子.
autorelease pool 與 RunLoop
程序運行 -> 開啟事件循環(huán) -> 發(fā)生觸摸事件 -> 創(chuàng)建自動釋放池 -> 處理觸摸事件 -> 事件對象加入自動釋放池 -> 一次事件循環(huán)結(jié)束, 銷毀自動釋放池.
在開始每一個事件循環(huán)之前系統(tǒng)會在主線程創(chuàng)建一個自動釋放池, 并且在事件循環(huán)結(jié)束的時候把前面創(chuàng)建的釋放池釋放, 回收內(nèi)存
蘋果在主線程 RunLoop 里注冊了兩個 Observer:
第一個 Observer 監(jiān)視的事件是 Entry(即將進入Loop),其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其 order 是-2147483647,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準備進入睡眠) 和 Exit(即將退出Loop),
BeforeWaiting(準備進入睡眠)時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;
Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
NSAutoreleasePool何時釋放??
分兩種情況:
1.手動加NSAutoreleasePool是在大括號結(jié)束釋放
2.沒手動加時在當前的runloop迭代時結(jié)束時候釋放
小實驗:
@property(nonatomic, weak)NSString *string_weak;
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? // 場景 1
? ? NSString *string = [NSString stringWithFormat:@"1234567890"];
? ? self.string_weak= string;
?? ? NSLog(@"string: %@",self.string_weak);
? ? //場景 2
? ? //? ? ? ? @autoreleasepool {
? ? //? ? ? ? ? ? NSString *string = [NSString stringWithFormat:@"1234567890"];
? ? //? ? ? ? _string_weak = string;
? ? //? ? ? ? }
? ? //? ? NSLog(@"string: %@",_string_weak);
? ? // 場景 3
? ? //? ? ? ? NSString *string = nil;
? ? //? ? ? ? @autoreleasepool {
? ? //? ? ? ? ? ? string = [NSString stringWithFormat:@”1234567890”];
? ? //? ? ? ? ? ? _string_weak = string;
? ? //? ? ? ? }
?// NSLog(@"string: %@",self.string_weak);
}
- (void)viewWillAppear:(BOOL)animated {
? ? [superviewWillAppear:animated];
? ? NSLog(@"string: %@", self.string_weak);
}
- (void)viewDidAppear:(BOOL)animated {
? ? [superviewDidAppear:animated];
? ? NSLog(@"string: %@", self.string_weak);
}
輸出:
場景一:
string: 1234567890
string: 1234567890
string: (null)
?分析:使用stringWithFormat,string對象加入當前runloop的自動釋放池,此時string對象的引用為strong和autoreleasepool;當作用域結(jié)束后strong引用用消失但autoreleasepool的引用還在,所以在viewWillAppear方法里面string: 1234567890;viewDidAppear方法時autoreleasepool銷毀,autoreleasepool的引用消失,此時string銷毀為null
場景二:
?string: (null)
string: (null)
string: (null)
分析:string在autoreleasepool的作用域結(jié)束后釋放
場景三:
string: 1234567890
string: (null)
string: (null)
分析:結(jié)合上面兩個場景可以分析出
autoreleasepool原理:
//將oc代碼翻譯成c++代碼
int main(int argc,char* argv[]) {
? ? @autoreleasepool{
? ? }
}
翻譯為:
int main(int argc,char* argv[]){
????????{
????????????????__AtAutoreleasePool __autoreleasepool;?
? ????????? }
????} //也就是生成了一個__AtAutoreleasePool類型的__autoreleasepool實例
?__AtAutoreleasePool是一個結(jié)構(gòu)體:
struct __AtAutoreleasePool {
? __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush();}
? ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
? void* atautoreleasepoolobj;
}; //__autoreleasepool中含有一個atautoreleasepoolobj指針,并且初始化和銷毀時分別調(diào)用了objc_autoreleasePoolPush()和Pop方法
已經(jīng)有人在蘋果的官方源碼里找到了關(guān)于AutoreleasePool的底層實現(xiàn)NSObject.mm里,源碼地址:https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html
void*objc_autoreleasePoolPush(void){
if(UseGC) returnNULL;//如果使用垃圾回收機制
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void*ctxt){
????if(UseGC)return;// fixme rdar://9167170
????if(!ctxt)return;?
?????AutoreleasePoolPage::pop(ctxt);
}
//也就是池子的創(chuàng)建和銷毀根本上是調(diào)用了AutoreleasePoolPage的push和pop方法
? ??????AutoreleasePoolPage中存放的是加入池子的oc對象
????????magic 用來校驗 AutoreleasePoolPage 的結(jié)構(gòu)是否完整;
????????next 棧頂,初始時在哨兵對象的下一個位置,每插入一個元素next都會是這個元素的下一個位置
????????begain 代表AutoreleasePoolPage的開始位置
????????end 代表AutoreleasePoolPage的結(jié)束位置,每個AutoreleasePoolPage會開辟4096字節(jié)內(nèi)存
????????thread 指向當前線程;
????????parent 指向父結(jié)點,第一個結(jié)點的 parent 值為 nil ;
????????child 指向子結(jié)點,最后一個結(jié)點的 child 值為 nil ;
????????depth 代表深度,從 0 開始,往后遞增 1;
????????hiwat 代表 high water mark 。
一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象,通過parent和child指針連接成鏈表,后來的autorelease對象在新的page加入。
上圖堆棧由三個 AutoreleasePoolPage 結(jié)點組成,第一個 AutoreleasePoolPage 結(jié)點為 coldPage() ,最后一個 AutoreleasePoolPage 結(jié)點為 hotPage(),總共有兩個 POOL_SENTINEL (哨兵)。
首先我來介紹幾個概念
1.POOL_SENTINEL(哨兵對象):
在每個自動釋放池初始化調(diào)用 objc_autoreleasePoolPush 的時候,都會把一個 POOL_SENTINEL push 到自動釋放池的棧頂,并且返回這個 POOL_SENTINEL 哨兵對象。
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
?objc_autoreleasePoolPop(atautoreleasepoolobj);
上面的 atautoreleasepoolobj 就是一個 POOL_SENTINEL
2.push 操作
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;
}
一個 push 操作其實就是創(chuàng)建一個新的 autoreleasepool ,對應(yīng) AutoreleasePoolPage 的具體實現(xiàn)就是往 AutoreleasePoolPage 中的 next 位置插入一個 POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的內(nèi)存地址,在執(zhí)行 pop 操作的時候作為函數(shù)的入?yún)ⅰ?/p>
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);
? ? }
}
autoreleaseFast 函數(shù)(將對象插入到池子)在執(zhí)行一個具體的插入操作時,分別對三種情況進行了不同的處理:
.當前 page 存在且沒有滿時,直接將對象添加到當前 page 中,即 next 指向的位置;
.當前 page 存在且已滿時,創(chuàng)建一個新的 page ,并將對象添加到新創(chuàng)建的 page 中;
.當前 page 不存在時,即還沒有 page 時,創(chuàng)建第一個 page ,并將對象添加到新創(chuàng)建的 page 中。
3.[obj?autorelease] 操作
- (id)autorelease {
? ? return ((id)self)->rootAutorelease();
}
id objc_object::rootAutorelease2(){
? ? assert(!isTaggedPointer());
? ? return AutoreleasePoolPage::autorelease((id)this);
}
AutoreleasePoolPage 的 autorelease 函數(shù)的實現(xiàn)對我們來說就比較容量理解了,它跟pool push 操作的實現(xiàn)非常相似。只不過pool push 操作插入的是一個 POOL_SENTINEL ,而 autorelease 操作插入的是一個具體的 autoreleased 對象。下面是AutoreleasePoolPage::autorelease代碼:
static inline id autorelease(id obj){
? ? assert(obj);
? ? assert(!obj->isTaggedPointer());
? ? id *dest __unused = autoreleaseFast(obj);
? ? assert(!dest? ||? *dest == obj);
? ? return obj;
}
4.pop操作
同理,前面提到的 objc_autoreleasePoolPop(void *) 函數(shù)本質(zhì)上也是調(diào)用的 AutoreleasePoolPage 的 pop 函數(shù)
void objc_autoreleasePoolPop(void *ctxt){
? ? if (UseGC) return;
? ? // fixme rdar://9167170
? ? if (!ctxt) return;
? ? AutoreleasePoolPage::pop(ctxt);
}
pop 函數(shù)的入?yún)⒕褪?push 函數(shù)的返回值,也就是 POOL_SENTINEL 的內(nèi)存地址,就是pool token 。當執(zhí)行 pop 操作時,內(nèi)存地址在 pool token 之后的所有 autoreleased 對象都會被 release 。直到 pool token 所在 page 的 next 指向 pool token 為止。
總結(jié):每當進行一次objc_autoreleasePoolPush調(diào)用時,runtime向當前的AutoreleasePoolPage中add進一個哨兵對象,當需要release時,objc_autoreleasePoolPop(哨兵對象)作為入?yún)ⅲ鶕?jù)傳入的哨兵對象地址找到哨兵對象所處的page。在當前page中,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次- release消息,并向回移動next指針到正確位置
代碼實戰(zhàn):
對象的autorelease方法會將當前對象加入棧頂?shù)腘SAutoreleasePool;NSAutoreleasePool是棧式管理,先入棧的后出棧,對NSAutoreleasePool對象發(fā)送release或者drain會銷毀棧頂?shù)腘SAutoreleasePool對象一直到銷毀當前的NSAutoreleasePool對象
子線程的autoreleasepool?
每個線程創(chuàng)建的時候就會創(chuàng)建一個autorelease pool,并且在線程退出的時候,清空autorelease pool。所以子線程的autorelease對象,要么在子線程中設(shè)置runloop清除,要么子線程結(jié)束時清除
TestObject:
+ (instancetype) instanceWithNumber:(NSInteger)number{
? ? //不加__autoreleasing 會因為arc返回值優(yōu)化而不是一個autorelease對象,可以去掉試試
? ? __autoreleasing TestObject*object = [[TestObject alloc]init];
? ? return object;
}
-(void)dealloc{
? ? NSLog(@"Test Object dealloc");
}
ViewController:
? __weak id _testObject;
? NSThread*_testThread;
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? _testThread = [[NSThread alloc] initWithTarget:self
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selector:@selector(testBuild)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? object:nil];
? ? [_testThread start];
? ? [self performSelector:@selector(testAlert) onThread:_testThread withObject:nil waitUntilDone:NO];
}
- (void)testBuild{
? ? TestObject*testObject = [TestObjectinstanceWithNumber:10];
? ? _testObject= testObject;
}
- (void)testAlert{
? ? NSLog(@"test alert");
? ? NSLog(@"test alert");
? ? NSLog(@"test alert");
}
如何詳細的查看堆棧信息?
在 Debug Navigator 下面有個搜框, 旁邊有三個按鈕, 把第一個按鈕點擊一下, 取消選中就可以了.