記憶體管理 Part 1
記憶體管理是一件極其簡單又極其麻煩的事情。
說它簡單,因為所謂的記憶體管理不過就是兩件事情:一塊記憶體我們是要用,還是不要用;該用的時候就用,不用的時候就釋放。麻煩的地方就在於我們很容易疏忽,而造成用與不用不成對。這就很像開車,開車也不過就是前進與煞車,但是天天路上都會發生車禍。
不成對,就會造成兩種結果:如果用完之後不釋放記憶體,就會造成軟體佔用了一堆沒有用到的記憶體,記憶體用量愈來愈大,造成記憶體用盡,在iOS 上系統會強制終止我們的應用程式,這種狀況叫做記憶體漏水(Memory Leak)。
反之,如果一塊記憶體已經被釋放掉了,我們卻還以為這塊記憶體還存在我們可以呼叫的物件,所以當我們嘗試呼叫的時候,才發現這塊記憶體該存在的物件已經不在了,這種狀況叫做over-release 或是 invalid memory reference,會造成應用程式 crash,crash log 上面會告訴你錯誤類型是 EXC_BAD_ACCESS
。
處理 over-release 可能是初學者開始學習 Mac OS X 與 iOS平台的第一個障礙(我覺得通常第二個障礙是搞不懂 delegate是什麼):一開始寫程式,程式卻一直莫名其妙的crash,於是前往網路論壇求救,但是通常也不會有多少人幫忙,因為網路上的其他同行通常都樂於回答問題,但懶得幫別人修bug。
過去三十年學界與業界也一直努力解決記憶體管理的問題,畢竟在寫軟體的時候,不把力氣放在處理程式邏輯問題,而是這種瑣碎的困擾,對於工程師的生產力是一大傷害,主要提出的解決方案是記憶體自動回收(Garbage Collection,GC),在軟體執行時,如果發現已經沒有任何一個變數指向某塊記憶體,就代表這塊記憶體再也用不到,於是開始回收這塊記憶體。90年代之後誕生的程式語言,幾乎都有 GC 機制。
蘋果曾經推出 Mac OS X 10.5 時,在 Mac OS X 上實作了GC,同時也帶動一些使用 GC 的動態語言開始與 Cocoa Framework 橋接,誕生了PyObjC、MacRuby、 RubyCocoa、JSTalk 專案,我們因此也可以使用Python、Ruby、JavaScript 等語言,直接撰寫 Mac OS X應用程式。但是,蘋果所實作的 GC,問題不小。
但我過去參與一項專案,在 Mac OS X 10.6 上面以 GC開發一個軟體,結果可說是淒慘無比:整個 Cocoa Framework 是架構在很多 C library 上的,而底層許多 library 其實並不支援 GC,我們發現只要在 main thread 之外用到像是
NSDateFormatter
、 NSNumberFormatter
這些class(這些 class 架構在
libICU 上,是 IBM 的一項 Open Source專案,用來處理多國語系的格式問題,同時也是一套 Regular Expression引擎),GC 就完全沒有作用,記憶體狂漏不止。蘋果在推出 iOS之後,也始終不在 iOS 上實作 GC。
隨著蘋果在 Compiler 的投資逐漸展露成果,逐步將 Compiler 從 GCC 換成 LLVM,最後決定改變技術方向,從另外一種方向來解決將記憶體管理自動化的問題,就是在iOS 5 上推出的 ARC(Automatic Reference Counting),不在 runtime回收記憶體,而是在編譯程式的時候,自動幫你加上與記憶體釋放有關的程式碼。而蘋果的下一步,就是直接在 ARC 的基礎上開發新的程式語言Swift。
雖然 ARC推出的目的就是希望你以後再也不要為記憶體管理問題煩惱,但問題是,實際上,你還是得知道記憶體管理是怎麼運作的。—記得有人打比方說,GC是自排車,而 Objective-C的記憶體管理就像開手排車,要自己知道怎麼進檔對檔,我覺得,加上 ARC呢,其實還是手排車,只是你訓練了一隻猴子幫你在副駕駛座幫你打檔,而你接下來,還得要學會怎麼調教這一隻猴子。
如果你在開發 iOS 或 Mac App 的時候,真的完全不想知道 Objective-C 的記憶體管理細節,可能就直接寫 Swift 吧!