本文來自南栀傾寒(簡書)的投稿,翻譯自蘋果Swift博客,原文:Memory Safety: Ensuring Values are Defined Before Use
歡迎通過“投稿爆料”渠道或者[email protected]投稿
我們在用swift設計開發時的一個重點就是如何提高編碼模型的內存安全問題。內存安全涉及到很多個方面,所以這篇文章會從一些基礎的內容開始,並包含一個簡單用例:如何確保變量有一個初始值才能使用。
swift版方法
變量什麼時候是安全的?開發者認為應該是無論任何時候訪問一個變量,它都有一個可用的有效值。語言采用不同的方法以確保安全,像其他編程語言,比如C,要求程序員對內存模型的編程技術非常嚴格,但是這是有風險的,人總會犯錯。C++和OC執行了強制性的模式來改善這種可能造成錯誤的地方 ,而其他語言則采取了一些極端的措施。
Swift使用的主要技術是使用高級編譯器執行代碼數據流分析。編譯器會強制變量在被使用之前進行初始化,這一策略被稱為Definitive Initialization。比如Java和C#(尤其)都采用了這種技術。Swift對更廣泛范圍內的變量使用了這種方法的擴展版本。
注意:文章末尾包含其他技術的信息,Swift在某種程度上使用了其中的大部分。
初始化局部變量
Swift在不少上下文環境中使用了Definitive Initialization的規則,不過局部變量使用的是最簡單的。Definitive Initialization比隱式默認初
始化規則更為靈活(參看下文),因為類型推斷允許你這樣寫:
var myInstance : MyClass // Uninitialized non-nullable class reference if x > 42 { myInstance = MyClass(intValue: 13) } else { myInstance = MyClass(floatValue: 92.3) } // Okay because myInstance is initialized on all paths myInstance.printIt()
編譯器可提供if語句兩端的驗證來初始化myInstance,可以保證不會調用那些未初始化的內存。
Definitive initialization是一個很強大的方法,但僅在它是可靠和可預測的時候非常有用。當你有更復雜的控制流時它會讓你大吃一驚,比如下邊:
var myInstance : MyClass if x > 10 { myInstance = MyClass(intValue: 13) } // ... if x > 42 { myInstance.printIt() }
這時編譯器可能告訴你 “Variable myInstance used before initialized”在調用printIt()。其實就是說變量未初始化,因為編譯器不可能做所有預測或者類型推斷,這就要求我們不要寫這麼復雜的邏輯去初始化一個變量。我們可以讓編譯器去處理個別的用例,不可能處理所有的用例(這麼做相當於halting problem),所以我們要保持編譯器規則的簡單和可預測性。
Swift讓初始化一個變量變得非常簡單。實際上,在普通數據類型如int初始化時可以直接寫var x = 0,給變量一個0初始值,而不是聲明未初始化變量var x : Int。可能的情況下,Swift支持初始化的外顯性。當使用init()調用時,就有很多強大的方法來初始化一個變量。你可以在Swift Programming Language的"Initialization" 一節中閱讀更復雜一些的內容。
其他技術補充
除了definitive initialization,Swift還在語言的重點范圍使用了附加方法。你可以在其他語言中使用這些技術,所以我們在這篇文章會說的很簡短。每種方法都有不足,所以它們不是Swift使用的主要方法:
將安全問題留給程序員:鑒於C語言的流行程度,所以理解simply leaving safety up to the developer的優缺點非常重要。不幸的是,使用未初始化的值在C語言中產生的未定義的行為(undefined behavior),通常會導致運行時問題(explosions)。C語言依賴程序員不犯錯誤。由於我們的目標是讓Swift默認是安全的,所以一般不會使用這個方法。不過,當明確需要的時候,類似UnsafePointer的API允許你明確地選擇不安全性。
隱式初始化:有些值可通過編譯器的隱式初始化來保證其安全性,比如設置一個“zero value”,類似Objective-C對實例變量做的那樣,或者通過運行默認構造器
(initializer)來實現,比如C++中的。我們對此進行了深度探索,但是最終決定不對其進行廣泛的使用,原因是:
如果有些Protocol沒有要求實現init()方法 這樣一個變量就可能引用一個未初始化的對象遺留在Cocoa編程中,這種情況在Swift中很常見。
即便是基本類型,比如整數0經常是錯誤值。這是在Swift中設置初始值如此簡單的一個原因。對於維護人員來說,就算不給變量默認值,自己寫一個也並不麻煩,而且會提前發現很多錯誤,使代碼更容易維護。
注意:對於可空類型值來說,默認初始化nil是正確的,所以所有Optional和ImplicitlyUnwrappedOptional類型會默認初始化為nil.
定義時要求構造器:在定義變量時,程序員通常要提供一個初始值,也就是說var x : Int沒有構造器是不合法的。雖然在函數式語言中這是一個常見的方法,不過這是個
非常沉重的要求,因為它強制執行了一個非常嚴格的編程風格,有礙自然模式的表達。