Manjusaka

Manjusaka

Swift 3.0 新增安全特性的普及介紹

Swift 發布之後,Swift 的開發者一直在強調,安全性與可選類型是 Swift 最為重要的特性之一。他們提供了一種nil的表示機制,並要求有一個明確的語法在可能為nil的實例上使用。

可選類型主要以下兩種:

  1. Optional
  2. ImplicitlyUnwrappedOptional

第一種做法是一種安全的做法:它要求我們去拆解可選類型變量是為了訪問基礎值。第二種做法是一種不安全的做法:我們可在不拆解可選類型變量的情況下直接訪問其底層值。比如,如果在變量值為 nil 的時候,使用 ImplicitlyUnwrappedOptional 可能會導致一些異常。

下面將展示一個關於這個問題的例子:


    let x: Int! = nil
    print(x) // Crash! `x` is nil!

Swift 3.0 中,蘋果改進了 ImplicitlyUnwrappedOptional 的實現,使其相對於以前變得更為安全。這裡我們不禁想問,蘋果到底在 Swift 3.0ImplicitlyUnwrappedOptional 做了哪些改進,從而使 Swift 變得更為安全了呢。答案在於,蘋果在編譯器對於 ImplicitlyUnwrappedOptional 進行類型推導的過程中進行了優化。

Swift 2.x 中的使用方式#

讓我們來通過一個例子來理解這裡面的變化。

    struct Person {
        let firstName: String
        let lastName: String

        init!(firstName: String, lastName: String) {
            guard !firstName.isEmpty && !lastName.isEmpty else {
                return nil
            }
            self.firstName = firstName
            self.lastName = lastName
        }
    }

這裡我們創建了一個初始化方法有缺陷的結構體 Person 。如果我們在初始化中不給實例提供 first namelast name 的值的話,那麼初始化將會失敗。

在這裡 init!(firstName: String, lastName: String) ,我們通過使用 ! 而不是 ? 來進行初始化的。不同於 Swift 3.0,在 Swift 2.x 中,我們用過利用 init! 來使用 ImplicitlyUnwrappedOptional 。不管我們所使用的 Swift 版本如何,我們應該謹慎的使用 init!。一般而言,如果你能允許在引用生成的為 nil 的實例時所產生的異常,那麼你可以使用 init! 。因為如果對應的實例為 nil 的時候,你使用 init! 會導致程序的崩潰。

.* 中,這個初始化方法將會生成一個 ImplicitlyUnwrappedOptional<Person> 。如果初始化失敗,所有基於 Person 的實例將會產生異常。

比如,在 Swift 2.x 裡,下面這段代碼在運行時將崩潰。

    // Swift 2.x

    let nilPerson = Person(firstName: "", lastName: "Mathias")
    nilPerson.firstName // Crash!

請注意,由於在初始化器中存在著隱式解包,因此我們沒有必要使用類型綁定(譯者注 1: optional binding )或者是自判斷鏈接(譯者注 2: optional chaining )來保證 nilPerson 能被正常的使用。

Swift 3.0 裡的新姿勢#

Swift 3.0 中事情發生了一點微小的變化。在 init! 中的 ! 表示初始化可能會失敗,如果成功進行了初始化,那麼生成的實例將被強制隱式拆包。不同於 Swift 2.xinit! 所生成的實例是 optional 而不是 ImplicitlyUnwrappedOptional 。這意味著你需要針對不同的基礎值對實例進行類型綁定或者是自判斷鏈接處理。


    // Swift 3.0

    let nilPerson = Person(firstName: "", lastName: "Mathias")
    nilPerson?.firstName

在上面這個示例中,nilPerson 是一個 Optional<Person> 類型的實例。這意味著如果你想正常的訪問裡面的值,你需要對 nilPerson 進行拆包處理。在這種情況下,手動拆包是個非常好的選擇。

安全的類型聲明#

這種變化可能會令人困惑。為什麼使用的 init! 的初始化會會生成 Optional 類型的實例?不是說在 init! 中的 ! 表示生成 ImplicitlyUnwrappedOptional 嗎?

答案是安全性與聲明之間的依賴關係。在上面這段代碼裡( let nilPerson = Person(firstName: "", lastName: "Mathias") )將依靠編譯器對 nilPerson 的類型進行推斷。

Swift 2.x 中,編譯器將會把 nilPerson 作為 ImplicitlyUnwrappedOptional<Person> 進行處理。講道理,我們已經習慣了這種編譯方式,而且它在某種程度上也是有道理的。總之一句話,在 Swift 2.x 中,想要使用 ImplicitlyUnwrappedOptional 的話,就需要利用 init! 對實例進行初始化。

然而,某種程度上來講,上面這種做法是很不安全的。說實話,我們從沒有任何釘定 nilPerson 應該是 ImplicitlyUnwrappedOptional 實例的意思,因為如果將來編譯器推導出一些不安全的類型信息導致程序運行出了偏差,等於,你們也有責任吧。

Swift 3.0 解決這類安全問題的方式是在我們不是明確的聲明一個 ImplicitlyUnwrappedOptional 時,會將 ImplicitlyUnwrappedOptional 作為 optional 進行處理。

限制 ImplicitlyUnwrappedOptional 的實例傳遞#

這種做法很巧妙的一點在於限制了隱式解包的 optional 實例的傳遞。參考下我們前面關於 Person 的代碼,同時思考下我們之前在 Swift 2.x 裡的一些做法:


    // Swift 2.x

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt.firstName // `matt` is `ImplicitlyUnwrappedOptional<person>`; we can access `firstName` directly</person>
    let anotherMatt = matt // `anotherMatt` is also `ImplicitlyUnwrappedOptional<person>`</person>

anotherMatt 是和 matt 一樣類型的實例。你可能已經預料到這種並不是很理想的情況。在代碼裡,ImplicitlyUnwrappedOptional 的實例已經進行了傳遞。對於所產生的新的不安全的代碼,我們務必要多加小心。

比如,在上面的代碼中,我們如果進行了一些異步操作,情況會怎麼樣呢?

    // Swift 2.x

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt.firstName // `matt` is `ImplicitlyUnwrappedOptional<person>`, and so we can access `firstName` directly</person>
    ... // Stuff happens; time passes; code executes; `matt` is set to nil
    let anotherMatt = matt // `anotherMatt` has the same type: `ImplicitlyUnwrappedOptional<person>`</person>

在上面這個例子中,anotherMatt 是一個值為 nil 的實例,這意味著任何直接訪問他基礎值的操作,都会導致崩潰。這種類型的訪問確切來說是 ImplicitlyUnwrappedOptional 所推薦的方式。那么我們如果把anotherMatt 換成 Optional<Person> ,情況會不會好一些呢?

讓我們在 Swift 3.0 中試試同樣的代碼會怎樣。


    // Swift 3.0

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt?.firstName // `matt` is `Optional<person>`</person>
    let anotherMatt = matt // `anotherMatt` is also `Optional<person>`</person>

如果我們沒有顯示聲明我們生成的是 ImplicitlyUnwrappedOptional 類型的實例,那麼編譯器會默認使用更為安全的 Optional

類型推斷應該是安全的#

在這個變化中,最大的好處在於編譯器的類型推斷不會使我們代碼的安全性降低。如果在必要的情況下,我們選擇的一些不太安全的方式,我們必須進行顯示的聲明。這樣編譯器不會再進行自動的判斷。

在某些時候,如果我們的確需要使用 ImplicitlyUnwrappedOptional 類型的實例,我們僅僅需要進行顯示聲明。

    // Swift 3.0

    let runningWithScissors: Person! = Person(firstName: "Edward", lastName: "") // Must explicitly declare Person!
    let safeAgain = runningWithScissors // What`s the type here?

runningWithScissors 是一個值為 nil 的實例,因為我們在初始化的時候,我們給 lastName 了一个空字符串。

請注意,我們所聲明的 runningWithScissors 實例是一個 ImplicitlyUnwrappedOptional<Person> 的實例。在 Swift 3.0 中,Swift 允許我們同時使用 OptionalImplicitlyUnwrappedOptional 。不過我們必須進行顯示聲明,從而告訴編譯器我們所使用的是 ImplicitlyUnwrappedOptional

不過幸運的是,編譯器不再自動將 safeAgain 作為一個 ImplicitlyUnwrappedOptional 實例進行處理。相對應的是,編譯器將會把 safeAgain 變量作為 Optional 實例進行處理。這個過程中,Swift 3.0 對不安全的實例的傳播進行了有效的限制。

一些想說的話#

ImplicitlyUnwrappedOptional 的改變可能是出於這樣一種原因:我們通常在 macOS 或者 iOS 上操作利用 Objective-C 所編寫的 API,在這些 API 中,某些情況下,它們的返回值可能是為 nil,對於 Swift 來講,這種情況是不安全的。

因此,Swift 正在避免這樣的不安全的情況發生。非常感謝 Swift 開發者對於 ImplicitlyUnwrappedOptional 所進行的改進。我們現在可以非常方便的去編寫健壯的代碼。也許在未來某一天,ImplicitlyUnwrappedOptional 可能會徹底的從我們視野裡消失。=

寫在最後的話#

如果你想知道更多關於這方面的知識,你可以從這裡this proposal獲取一些有用的信息。你可以從 issue 裡獲得這個提案的作者的一些想法,同時通過具體的變化來了解更多的細節。同時那裡也有相關社區討論的鏈接。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。