ARC中的@autoreleasepool

Hello!小夥伴們,大家好呀!歡迎來到暴走…………(sorry,拿錯劇本了)。咳咳,大家好呀!今天呢,我要來介紹一個非常古老的東西——Objective-C中的自動釋放池。

我們拿起Xcode默認生成的HelloWorld代碼我們就能夠看到:

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        // start your code here...
    }
    return 0;
}
對於初學者來說,這個@autoreleasepool看起來實在是很費解啊。在實際的代碼編寫中,貌似無視掉(甚至去掉)這個@autoreleasepool好像也沒什麼影響嘛。那麼這玩意到底是個神馬?

這個問題呢,需要追溯到iOS 4以前,那個時候我們在使用Objective-C編寫iOS和Mac OS X程序時,都是手動的進行內存管理,我們稱其爲:MRC(Mannul Reference Counting),內存管理機制爲引用計數器,舉一個例子(由於本文的重點不在於介紹MRC,因此只做概述不進行詳細講解):

@import Cocoa;

@interface Test : NSObject

@end

@implementation Test

- (void)dealloc {
    NSLog(@"析構");
    [super dealloc];
}

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"構造");
    }
    return self;
}

@end

int main(int argc, const char * argv[]) {
    Test *t = [[Test alloc] init]; // 構造,引用計數+1
    [t retain]; // 引用計數+1
    [t release]; // 引用計數-1
    [t release]; // 引用計數-1,歸零,析構
    return 0;
}
如果創建的對象比較多,而它們又需要在一個統一的時間進行大量的release時,用這種方法管理內存就會使得代碼過於冗長,降低可讀性,因此,我們使用自動釋放池來統一管理。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test *t1 = [[[Test alloc] init] autorelease]; // 構造,引用計數+1
        Test *t2 = [[[Test alloc] init] autorelease]; // 構造,引用計數+1
    } // 將所有調用過autorelease方法的對象全部釋放,t1和t2析構
    return 0;
}
這裏強調一個特殊情況,如果一個沒有被調用autorelease方法的對象中途沒有了指向的指針,則該對象在程序運行結束之前無法被釋放,這樣就會出現內存泄露。
int main(int argc, const char * argv[]) {
    Test *t1 = [[Test alloc] init];
    t1 = nil; // 指針脫離對象,對象無法再次被指向,因此無法被釋放,產生內存泄露
    [[Test alloc] init]; // 同樣產生內存泄露
    return 0;
}
但是,如果在指針脫離前,該對象處於自動釋放池中,並且調用過autorelease方法,則不會出現這樣的問題
@autoreleasepool {
        id t = [[[Test alloc] init] autorelease];
        t = nil;
        //......
    } // 在這裏仍然會釋放對象
這裏搞明白以後我們再來看看ARC的情況。ARC把指向對象的指針分成了三類,分別是:strong,weak,autoreleasing。接下來將逐一介紹。

如果一個指針被聲明爲strong(__strong關鍵字爲默認,可以省略),那麼當這個這個指針指向某一對象時,該對象的引用計數器會+1,而當一個strong指針不再指向這個對象時,該對象的引用計數器會自動-1,請仔細觀察以下的情況:

int main(int argc, const char * argv[]) {
    Test __strong *t1 = [[Test alloc] init]; // 創建對象,同時有strong指針指向對象,引用計數+1
    {
        Test __strong *t2 = t1; // 又有一個strong指針指向了對象,引用計數器+1
    } // 代碼塊結束,局部變量的生命週期結束,t2被銷燬,對象少了一個strong指針去指向,引用計數-1
    t1 = [[Test alloc] init]; // t1指向了新的對象,原對象少了一個strong指針指向,引用計數-1,由於引用計數歸零,對象析構
    t1 = nil; // t1清空,後創建的對象引用計數器-1,也被銷燬
    return 0;
}
如果一個指針被聲明爲weak,那麼該指針的指向不會影響對象的引用計數器,當weak指針指向的對象被釋放時,該指針自動清空。這種類型的指針的出現主要是爲了解決循環引用的問題(這裏不多做闡述)
int main(int argc, const char * argv[]) {
    Test __strong *t1 = [[Test alloc] init]; // 創建對象,同時有strong指針指向對象,引用計數+1
    Test __weak *t2 = t1; // weak指針指向對象,引用計數器不變
    t1 = [[Test alloc] init]; // t1指向新對象,原對象引用計數器-1,歸零,釋放,這時,t2指針將會自動被賦值爲nil,ARC的這種處理機制可以避免野指針(也叫垂懸指針)
    [t2 discardEditing]; // 向一個空指針(注意這裏不是野指針)發送消息,不會產生錯誤,也不會得到迴應
    
    return 0;
}
如果一個指針被聲明爲autoreleasing,那麼該指針指向的對象將會被註冊到當前的自動釋放池中(如果當前不在自動釋放池中,則相當於一個普通指針,這種情況下通常會產生內存泄露),當自動釋放池結束時,會對所有註冊到該池的對象進行引用計數器-1的操作
int main(int argc, const char * argv[]) {
    id __weak t1; // 定義了一個weak指針
    id t = [[Test alloc] init]; // 定義個一個strong指針,並指向一個新創建的對象,引用計數器+1
    @autoreleasepool { // 定義了一個自動釋放池
        id __autoreleasing t2 = t; // 定義了一個autoreleasing指針,並指向對象,引用計數器+1,並將對象註冊到自動釋放池中
        t1 = t2; // 用一個weak指針指向對象,引用計數器不變
        t2 = nil; // 這裏將autoreleasing指針清空,對原對象的引用計數器沒有影響,但是由於該對象已經被註冊到自動釋放池中,因此不會產生內存泄露
        t = nil; // strong指針清空,對象的引用計數器-1
    } // 退出自動釋放池,對象的引用計數器-1,歸零,釋放,t1指針自動清空
    [t1 discardEditing]; // 此時的t1成爲了nil,因此向其發送消息不會得到回覆,也不會產生錯誤
    return 0;
}
相信看到這裏,你應該明白這個神奇的@autoreleasepool是什麼東西了吧。值得一提的是,雖然ARC機制極力避免出現內存泄露以及野指針的情況,但在某些情況下還是會出現,例如:
    id __autoreleasing t;
    @autoreleasepool {
        t = [[Test alloc] init];
    } // 對象在這裏釋放
    [t show]; // 由於t並不是weak型,因此會成爲野指針,發送消息會引發致命錯誤
int main(int argc, const char * argv[]) {
    id __autoreleasing t = [[Test alloc] init]; // 由於t沒有處於任何的自動釋放池,因此該對象無法被釋放,會產生內存泄露
    return 0;
}
最後要說的一點是,自動釋放池本身也是一個代碼塊,其中定義的變量(包括指針)如果沒有用static關鍵字修飾的話,都會在代碼塊結束時被釋放,我們在使用時要小心
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 5;
    }
    NSLog(@"%d", a); // 這裏會報錯,因爲自動釋放池中定義的a在這裏沒有作用域
    return 0;
}

關於ARC中的@autoreleasepool今天就講到這裏啦。其實我本人前陣子也是一直在爲它發愁,查了很多資料都沒有正面闡述在ARC中自動釋放池的作用,網上搜出來的也總是千篇一律,不解決問題,所以只能自己慢慢嘗試,慢慢研究。今天把研究的成功寫成博客,希望能夠幫到更多像我一樣的人。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章