透過 Objective-C 快速了解 iOS 記憶體管理的概念


本篇原文(標題:iOS Memory Management in Under 6 Minutes)刊登於作者 Medium,由 Hassan El Desouky 所著,並授權翻譯及轉載。

最近,我搜尋了很多關於 iOS 記憶體管理的資訊,這是 Swift 中對我來說有點困難的題目。因此,我決定要更深入了解 Objective-C,因為我已經學過它,所以可以更透徹地了解記憶體管理,像是在 ARC 之前是如何進行記憶體管理的呢?有了 ARC 之後的情況又是如何?ARC 與垃圾回收器 (Garbage Collector) 有甚麼差異?諸如此類的問題。

簡介

當你開始撰寫物件 (Object) 時,會發現一件有趣的事。

物件比整數 (integer)、浮點 (float) 這類原始型別需要更多記憶體,無論這些物件是來自你自己建構的類別,或是創建自任何 Apple 框架的類別都是如此。

幸運的是,我們不必擔心要手動使用記憶體位置、及手動分配或解除分配記憶體區域(對某些語言而言)。而在 Objective-C 中,我們則使用參考計數 (Reference Counting)。

甚麼是參考計數?

當你的 App 啟動時,就會為你的物件提供一個記憶體區域。也就是說,你建立一個物件的同時,會在記憶體內要求一個區塊。現在,持有那個物件的變數就是指向該物件的指標,準確來說,是指向一個記憶體區域的指標。實際情況是,當物件被建立時,它會被授予一個稱作保留計數 (retain count)參考計數的東西,而這個東西表示特定物件的所有者數量。因此,我們可以想像它會是 1,如下圖所示:

memory-management-1

這個名為 var 的變數,是一個記憶體區塊的指標。所以在程式碼中,你可以使用那個指標,並按需要呼叫方法。然而,當你到達程式碼區塊的最後時,這個變數將不在能被任何程式使用。這時,保留計數會回歸為 0,因為現在沒有任何東西指向這個記憶體區塊。而當運行引擎 (Runtime Engine) 說保留計數為 0 時,就沒有人在意這個記憶體區塊了,因此這個記憶體區塊將會被釋放,讓其他的物件使用。

可能會發生甚麼問題?

這裡唯一可能會出現問題的時間點,就是當你在建立物件,並將其從一處送到另一處時,所以我們不清楚指標是否仍在記憶體範圍內。直到 2011 年 ARC (Automatic Reference Counting) 功能出現之前,你都需要在用完物件時撰寫一些程式碼。

在 ARC 之前

在 ARC 功能加進 Objective-C 之前,我們必須做些這樣的事情:

MyClass *myObject = [[MyClass alloc] init];
[myObject myMethod]; // call methods
... // doing some stuff with the object
[myObject release]; // releasing the object

我們需要寫些程式碼來建立物件,物件建立了之後,就要呼叫物件的方法。但在某些時候,我們必須明確地呼叫 release 陳述句,這就是保留計數數字減少的原因。

如果物件數量少,這就不會是個問題。但是如果你擁有數百、甚至數千個物件被建立、操作、用為參數、或是在物件之間傳送,你就需要一直追蹤他們。如果想將一個物件從一個區域傳送到另一個區域,你可能無法確定是否可以釋放它、或是程式的其他部分是否會負責釋放,或甚至可能在你使用完之前就把它釋放了。因此,你也可以撰寫呼叫的內容,並保留對該物件的呼叫,但你仍需要將任何保留呼叫 (Retain Call) 與其他的釋放呼叫 (Release Call) 配對。

基本上來說,在 ARC 之前,你必須設想 App 可能經歷的每個情境邏輯 (scenario-logic),以確保所有物件的生命週期都被正確地管理,不過這並不容易。

有了 ARC 之後

幸運的是,有了 ARC 之後,你不再需要使用 releaseautoreleaseretain 這些呼叫。但重要的是,你要了解錯誤編寫這些程式碼可以造成的危險。

其中一個你可能遇到的問題,是你可能太快釋放。你會建立一個物件,然後有個指標指向記憶體區域,接著你會呼叫方法,最後在某個時間點釋放它。但是,如果你仍然持有一個有效的指標,那麼就沒有甚麼可以阻止你撰寫另一行程式碼來使用它。這名為迷途指標 (Dangling Pointer),指的是指標依舊存在,但它指向的記憶體區域不再有效,而這可能會導致閃退。

另外一個相反的問題,則是你可能沒有釋放,卻又建立了物件。開始呼叫物件的方法,然後讓指標脫離記憶體區域並消失,但你卻沒有釋放物件,這樣你就會需要越來越多記憶體,導致所謂的記憶體洩漏 (Memory Leak)

所以,寫太多這樣的程式碼肯定容易出錯。現在你可能在想,明明我們正在使用這個新的 ARC 功能,為什麼我卻在說釋放以及保留呼叫的事呢。因為釋放及保留呼叫的概念沒有消失,這個程式語言仍會進行參考計數。

程式語言並沒有改變。有了 ARC 之後,不同之處只是你不再需要撰寫 retain、release、autorelease 呼叫語法,因為編譯器幫你寫了。ARC 認為編譯器的能力已經非常好,好到每當你編譯專案時,編譯器(這裡是 llvm,也就是 Xcode 在背後使用的東西)能夠確認程式碼的所有路徑。而且,它基本上遍歷你的程式碼,來整合所需的寫入、retain、release、autorelease 呼叫。

如果你真的非常擅於撰寫記憶體管理的程式碼,那麼編譯器所做的,就是更有效地寫出相同的程式碼。

ARC 實際上不會更改你的原始程式碼檔,但你在使用 ARC 編譯專案時,這就是編譯器做的事。

ARC 與垃圾回收器的差異

ARC 與垃圾回收器的效果截然不同。使用垃圾回收器的語言,通常是被稱為不確定性的 (Non-Deterministic) 語言。這意味著你無法準確地告知物件何時被回收,因為它是由一個外部程序在運行時管理的。而 ARC 則使我們是完全確定性的,程式碼會控制這些物件何時被釋放,因為釋放它們的程式碼是由編譯器撰寫,而不是你。事實上,不僅是你使用 ARC 時不用寫這些呼叫,而是你無法撰寫這些呼叫。如果你試著寫簡單的釋放呼叫,那麼將會碰到錯誤。

總結

你需要了解保留與釋放的概念,這些事情仍然在背後發生。不過當然,如果你從來沒有需要親手這樣做過,你應該感恩以後也沒有需要這樣做了。

希望你們可以留言分享你對這個題目的想法。你認為回到像 Objective-C 這樣的語言,是否可以更容易了解記憶體管理的題目?你有沒有試過這樣做?

本篇原文(標題:iOS Memory Management in Under 6 Minutes)刊登於作者 Medium,由 Hassan El Desouky 所著,並授權翻譯及轉載。

作者簡介:Hassan El Desouky,是一名電腦科學系學生,也是一名開發者,性格樂觀,深信「習慣決定你的未來」。

譯者簡介:楊敦凱-目前於科技公司擔任 iOS Developer,工作之餘開發自有 iOS App 同時關注網路上有趣的新玩意、話題及科技資訊。平時的興趣則是與自身專業無關的歷史、地理、棒球。來信請寄到:[email protected]


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This