iOS 與 OSX 內存管理:引用計數

做者:Andyy Hope,原文連接,原文日期:2016-02-23
譯者:wiilen;校對:小鍋;定稿:CMBios

在 2009 年,我第一次下定決心要學習如何開發 App。那時候 iOS 3 纔剛剛發佈,以後,App Store 就成了那些開發 to-do 列表、筆記記錄以及其它無聊應用的開發者們的金礦。git

Objective-C 是我決定要全身心投入學習的第一門面向對象的語言,那時候這門語言與如今有不少區別。過去幾年咱們見證了它的發展,與此同時蘋果還發布了使人印象深入的 Swift 語言。github

現在開發者們認爲 ARC(自動引用計數)是理所應當的存在,特別是那些在 iOS 5 發佈(2011年)以後學習 Objective-C 的人,或是學習 Swift 的人。objective-c

什麼是引用計數?

引用計數是計算機科學中的一種技術,經過這種技術,每一個對象在實例化時都被分配了一個計數值,所以應用程序能夠知道哪些對象仍在使用。在對象的生命週期中,其它對象若是須要使用該對象,就聲明對該它的全部權,而後增長計數值;當該對象完成了任務以後釋放(release)全部權,而後減小計數值。一個對象的計數值減爲 0 時,就會從內存中銷燬(deallocate)。舉個例子:swift

objc
- (void)demonstration {
    MyClass *foo = [[MyClass alloc] init];
    [foo performSomeMethod];
    [foo release];
}

在上面的代碼中,咱們作了三件事:初始化一個對象,調用它的某個方法,最後釋放它。在 iOS 5 發佈以前,開發者們在開發應用時都須要這麼作。這種內存管理的方式被稱爲手動引用計數,即 MRC(Manual Reference Counting) 或 MRM(Manual Reference Management)。app

在看了上面的代碼以後,你的第一印象可能以爲這麼作並無什麼大不了,由於它很簡單。然而當代碼量持續增長,而且更多開發者參與到項目中時,開發者更有可能在這裏出錯。函數

手動引用計數

因此 MRC 中具體包含了哪些內容呢?學習

alloc

objc
MyClass *foo = [[MyClass alloc] init];

這段代碼是 Objective-C 中最最基礎的。要建立一個對象,首先須要初始化它。當調用 alloc 來建立一個對象時,系統會爲該對象分配內存空間,並將它的引用計數設爲 1。翻譯

release

objective-c
[foo release];

對象調用 release 會使它的引用計數減 1。當對象的引用計數減爲 0 時,系統會將該對象從內存移除,並釋放內存空間以供其它對象使用。指針

retain

objc
[foo retain];

對象調用 retain 時,會通知系統爲它的引用計數加 1。調用 retain 意味着其它對象想要持有 foo

咱們假設兩個不一樣的對象都持有 foo,當第一個對象對 foo 調用 release 時,foo 的引用計數會從 2 減爲 1。第二個對象仍然可使用 foo,而無需擔憂 carsh 或產生一個懸空指針。

copy

objc
MyClass *bar = [foo copy];

copyretain 的原理很類似,它能夠複製一份原來的對象,不一樣之處在於引用計數。若是複製 foo 時它的引用計數爲 4,複製獲得的對象 bar 的引用計數只會爲 1。

autorelease

objc
[foo autorelease];

當一個對象的做用域超出了它所聲明的範圍,就須要對其調用 autorelease。它會告訴系統,咱們並不但願當即銷燬這個對象,而是在 autoreleasepool 被清空的時候再去銷燬這個對象。

autorelase 一般當咱們在一個方法內部聲明一個對象並將其返回給其調用者時使用。另外一個情景是對象在 for 循環中實例化,而且該循環中有一個 autoreleasepool 時,也可使用 autorelease

objc
- (MyClass *)foo {
    MyClass *foo = [[MyClass alloc] init];
    [foo autorelease];
}

autoreleasepool

objc
- (void)example {
    for (int i = 0, i < 10, i++) {
        @autoreleasepool {
            MyClass *foo = [[MyClass alloc] init];
            [foo autorelease];
        }
    }
}

上面的代碼中,咱們實例化了 foo 對象,並對其調用了 autorelease。這些操做被包裹在 autoreleasepool 中,外層還有一個 for 循環。

這麼作的好處在於,for 循環中實例化的全部對象,能夠在每一輪循環結束時自動被釋放。當這一切發生時 autoreleasepool 會本身進行銷燬,並釋放全部對象,恢復到原來乾淨整潔的狀態。

dealloc

objc
- (void)dealloc {
    [foo release];
    [bar release];
    [fubar release];
    [super dealloc];
}

dealloc 是全部繼承自 NSObject 的對象最後會調用的方法。你能夠把 dealloc 想象成清理那些引用計數大於 0 的遺留對象的地方。

手動引用計數的缺點

如今你應該能更好的理解手動管理引用計數所須要作的工做。下面介紹兩個常見場景,關於微小的人爲失誤可能致使運行時 crash 的狀況。

懸空指針

objc
MyClass *foo = [[MyClass alloc] init];
[foo release];
[foo doSomething];

以前提到過,若是對象的引用計數減爲 0,系統會將該對象從內存移除。該地址的內存空間清空以後,可能保留着仍爲空的狀態,也可能有其它對象佔據了這塊空間。

可是要記住一點, foo 指針仍然指向這塊內存。因此當 doSomething 方法被調用時,實際上會讓 nil 或其它佔據這塊內存的對象去調用這個方法,這樣作經常會引發 crash。

內存泄漏

objc
MyClass *foo = [[MyClass alloc] init];
[foo retain];
[foo retain];
[foo release];

這有點像懸空指針的反面狀況,當對象調用 release 的次數少於調用 retain 的次數時,就會發生內存泄漏。若是對象的引用計數一直不減爲 0,系統就沒法把該對象的資源分配給其它對象。

你的應用中有些對象永遠沒有被釋放,看上去好像沒有多大的問題,不過若是這樣的對象太多,就會致使應用的內存被耗光,產生一些奇怪的問題,並最終致使 crash。這也是咱們在 dealloc 中執行清空操做的緣由。

關於自動引用計數

Session 323 - iOS,OS X

2011 年,舊金山六月的一個使人愉快的早晨,你們在 Hall-H 參加了每一年一度的 WWDC。Phil Schiller 展現了新的 Mail app,Scott Forstall 展現了 Game Center 的漂亮 UI,不幸的是那也是 Steve Jobs 的最後一場 keynote?。那場 Keynote 中展現了大量新內容,但沒有 ARC 這個對開發者而言的大驚喜。這周晚些時候,開發者們也參加了關於 iOS 與 OS X 的 Session 323,他們的生活今後發生了巨大的變化。

自動化的魔法

直到今天,全部 iOS 與 OS X 應用仍然使用引用計數,惟一不一樣在於咱們再也不須要進行手動管理,由於編譯器都幫咱們作好了。

這裏嚴肅聲明一點,全部我以前提到的語句,編譯器在編譯時會幫咱們自動插入,包括 retainreleasecopyautoreleaseautoreleasepool。若是你回去看看我提供的演示代碼,想象上面五種調用都被註釋了,簡而言之 ARC 都幫咱們處理好了。繼續前進吧,初學者們,自由地寫 app,不用再擔憂內存管理!除了循環引用以外你不須要再擔憂什麼,不過那是下一節的內容了。


我已經在 GitHub 中上傳了一些樣例,你能夠下載看看其中內存管理的語法。這只是一個簡單的 OS X 控制檯程序,其中 ARC 已經關閉了。示例函數的調用已經都被註釋了,若是你想要看看它們的運行效果,只須要取消註釋,編譯並運行。盡情體驗吧!

本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg

相關文章
相關標籤/搜索