Swift3.0 - 可選類(lèi)型

在編程世界中有一種非常通用的模式,那就是某個(gè)操作是否要返回一個(gè)有效值。

Objective-C 中,對(duì) nil 發(fā)送消息是安全的。如果這個(gè)消息簽名返回一個(gè)對(duì)象,那么
nil 會(huì)被返回;如果消息返回的是一個(gè)結(jié)構(gòu)體那么它的值都將為零。但在實(shí)際開(kāi)發(fā)中我們常常會(huì)碰到由于對(duì)象為 nil 而導(dǎo)致的崩潰或者不想出現(xiàn)的問(wèn)題,比如數(shù)組、字典、block的判空處理等等。

Tony Hoare在1965年設(shè)計(jì)了 null 引用,他對(duì)此設(shè)計(jì)表示痛心疾首,并將這個(gè)問(wèn)題稱(chēng)為“價(jià)值10億美元的錯(cuò)誤”:

那時(shí)候,我正在為一門(mén)面向?qū)ο蟮恼Z(yǔ)言(ALGOL W)設(shè)計(jì)第一個(gè)全面的引用類(lèi)型系統(tǒng)。我的目標(biāo)是在編譯器自動(dòng)執(zhí)行的檢查的保證下,確保對(duì)于引用的所有使用都是安全的。但是我沒(méi)能抵擋住 null 引用的誘惑,因?yàn)樗菀讓?shí)現(xiàn)了。這導(dǎo)致了不計(jì)其數(shù)的錯(cuò)誤,漏洞以及系統(tǒng)崩潰。這個(gè)問(wèn)題可能在過(guò)去四十年里造成了有十億美元的損失。

前面敘述這么多是為了引出編程語(yǔ)言中更安全的設(shè)計(jì) - swift中的可選類(lèi)型

swift定義后綴 ? 來(lái)作為標(biāo)準(zhǔn)庫(kù)中的定義的命名型類(lèi)型 Optional<Wrapped>語(yǔ)法糖。換句話說(shuō),下面兩個(gè) 聲明是等價(jià)的:

var optionalInteger: Int?
var optionalInteger: Optional<Int>

在上述兩種情況下,變量 optionalInteger 都被聲明為可選整型類(lèi)型。注意在類(lèi)型和 ? 之間沒(méi)有空格。

類(lèi)型 Optional<Wrapped> 是一個(gè)枚舉,有兩個(gè)成員,nonesome(Wrapped),用來(lái)表示可能有也可能沒(méi)有的值。任意類(lèi)型都可以被顯式地聲明(或隱式地轉(zhuǎn)換)為可選類(lèi)型。如果你在聲明或定義可選變量或?qū)傩缘臅r(shí)候沒(méi)有提供初始值,它的值則會(huì)自動(dòng)賦為默認(rèn)值 nil 。

如果一個(gè)可選類(lèi)型的實(shí)例包含一個(gè)值,那么你就可以使用后綴運(yùn)算符 ! 來(lái)獲取該值(即強(qiáng)制解包),正如下面描述的:

optionalInteger = 42
optionalInteger! // 42

使用 ! 運(yùn)算符解包值為 nil 的可選值會(huì)導(dǎo)致運(yùn)行錯(cuò)誤。

你也可以使用可選鏈?zhǔn)秸{(diào)用和可選綁定來(lái)選擇性地在可選表達(dá)式上執(zhí)行操作。如果值為 nil ,不會(huì)執(zhí)行任何操作,因此也就沒(méi)有運(yùn)行錯(cuò)誤產(chǎn)生。

  • 處理可選值值缺失的方法

一個(gè)可選的值是一個(gè)具體的值或者是 nil 以表示值缺失。在類(lèi)型后面加一個(gè)問(wèn)號(hào)來(lái)標(biāo)記這個(gè)變量的值是可選的。你可以一起使用 iflet來(lái)處理值缺失的情況:

var optionalString: String? = "Hello"
print(optionalString == nil)  //false

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

上面的代碼中如果變量optionalName的可選值是 nil,條件會(huì)判斷為 false,大括號(hào)中的代碼會(huì)被跳過(guò)。如果不是 nil,會(huì)將值解包并賦給 let 后面的常量 name,這樣代碼塊中就可以使用這個(gè)值了。

還有另外一種處理值缺失的方法,使用 ?? 操作符來(lái)提供一個(gè)默認(rèn)值。如果可選值缺失的話,可以使用默認(rèn)值來(lái)代替。

 let nickName: String? = nil
 let fullName: String = "John Appleseed"
 let informalGreeting = "Hi \(nickName ?? fullName)"

因?yàn)?nickNamenil,所以 informalGreeting的值就變成了 "Hi John Appleseed"。

  • 隱式解析可選類(lèi)型

當(dāng)可以被訪問(wèn)時(shí),Swift 語(yǔ)言定義后綴 ! 作為標(biāo)準(zhǔn)庫(kù)中命名類(lèi)型 Optional<Wrapped>的語(yǔ)法糖,來(lái)實(shí)現(xiàn)自動(dòng) 解包的功能。換句話說(shuō),下面兩個(gè)聲明等價(jià):

var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>

由于隱式解包修改了包涵其類(lèi)型的聲明語(yǔ)義,嵌套在元組類(lèi)型或泛型的可選類(lèi)型(比如字典元素類(lèi)型或數(shù)組元素
類(lèi)型),不能被標(biāo)記為隱式解包。例如:

let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 錯(cuò)誤
let implicitlyUnwrappedTuple: (Int, Int)! // 正確
let arrayOfImplicitlyUnwrappedElements: [Int!] // 錯(cuò)誤 
let implicitlyUnwrappedArray: [Int]! // 正確

由于隱式解析可選類(lèi)型和可選類(lèi)型有同樣的表達(dá)式析可選類(lèi)型。比如,你可以將隱式解析可選類(lèi)型的值賦給變量、常量和可選屬性,反之亦然。

正如可選類(lèi)型一樣,你在聲明隱式解析可選類(lèi)型的變量或?qū)傩缘臅r(shí)候也不用指定初始值,因?yàn)樗心J(rèn)值 nil
可以使用可選鏈?zhǔn)秸{(diào)用來(lái)在隱式解析可選表達(dá)式上選擇性地執(zhí)行操作。如果值為 nil ,就不會(huì)執(zhí)行任何操作,因此也不會(huì)產(chǎn)生運(yùn)行錯(cuò)誤。

  • 使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制展開(kāi)

通過(guò)在想調(diào)用的屬性、方法、或下標(biāo)的可選值后面放一個(gè)問(wèn)號(hào) ? ,可以定義一個(gè)可選鏈。這一點(diǎn)很像在可選 值后面放一個(gè)嘆號(hào) ! 來(lái)強(qiáng)制展開(kāi)它的值。它們的主要區(qū)別在于當(dāng)可選值為空時(shí)可選鏈?zhǔn)秸{(diào)用只會(huì)調(diào)用失敗,然而強(qiáng)制展開(kāi)將會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

為了反映可選鏈?zhǔn)秸{(diào)用可以在空值 nil 上調(diào)用的事實(shí),不論這個(gè)調(diào)用的屬性、方法及下標(biāo)返回的值是不是可選值,它的返回結(jié)果都是一個(gè)可選值。你可以利用這個(gè)返回值來(lái)判斷你的可選鏈?zhǔn)秸{(diào)用是否調(diào)用成功,如果調(diào)用 有返回值則說(shuō)明調(diào)用成功,返回 nil 則說(shuō)明調(diào)用失敗。

特別地,可選鏈?zhǔn)秸{(diào)用的返回結(jié)果與原本的返回結(jié)果具有相同的類(lèi)型,但是被包裝成了一個(gè)可選值。例如,使用 可選鏈?zhǔn)秸{(diào)用訪問(wèn)屬性,當(dāng)可選鏈?zhǔn)秸{(diào)用成功時(shí),如果屬性原本的返回結(jié)果是 Int 類(lèi)型,則會(huì)變?yōu)?Int? 類(lèi)型。

代碼示例,首先定義兩個(gè)類(lèi) PersonResidence :

 class Person {
     var residence: Residence?
}
 class Residence {
     var numberOfRooms = 1
}

Residence 有一個(gè) Int 類(lèi)型的屬性 numberOfRooms ,其默認(rèn)值為 1 。 Person 具有一個(gè)可選的 residence 屬性,其類(lèi)型為 Residence?

假如你創(chuàng)建了一個(gè)新的 Person 實(shí)例,它的 residence 屬性由于是是可選型而將初始化為 nil ,在下面的代碼中, john 有一個(gè)值為 nilresidence 屬性:

 let john = Person()

如果使用嘆號(hào) ! 強(qiáng)制展開(kāi)獲得這個(gè) johnresidence 屬性中的 numberOfRooms 值,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤,因?yàn)檫@時(shí) residence 沒(méi)有可以展開(kāi)的值:

let roomCount = john.residence!.numberOfRooms // 這會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤

john.residence 為非 nil 值的時(shí)候,上面的調(diào)用會(huì)成功,并且把 roomCount 設(shè)置為 Int 類(lèi)型的房間數(shù)量。正如上面提到的,當(dāng) residencenil 的時(shí)候上面這段代碼會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

可選鏈?zhǔn)秸{(diào)用提供了另一種訪問(wèn) numberOfRooms 的方式,使用問(wèn)號(hào) ? 來(lái)替代原來(lái)的嘆號(hào) ! :

if let roomCount = john.residence?.numberOfRooms {
     print("John's residence has \(roomCount) room(s).")
 } else {
     print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”

residence 后面添加問(wèn)號(hào)之后,Swift 就會(huì)在 residence 不為 nil 的情況下訪問(wèn) numberOfRooms 。

因?yàn)樵L問(wèn) numberOfRooms 有可能失敗,可選鏈?zhǔn)秸{(diào)用會(huì)返回 Int? 類(lèi)型,或稱(chēng)為“可選的 Int ”。如上例所 示,當(dāng) residencenil 的時(shí)候,可選的 Int 將會(huì)為 nil ,表明無(wú)法訪問(wèn) numberOfRooms 。訪問(wèn)成功時(shí),可選的
Int 值會(huì)通過(guò)可選綁定展開(kāi),并賦值給非可選類(lèi)型的 roomCount 常量。

要注意的是,即使 numberOfRooms 是非可選的 Int 時(shí),這一點(diǎn)也成立。只要使用可選鏈?zhǔn)秸{(diào)用就意味著 numberOfRooms 會(huì)返回一個(gè) Int? 而不是 Int

可以將一個(gè) Residence 的實(shí)例賦給 john.residence ,這樣它就不再是 nil 了:

john.residence = Residence()

john.residence 現(xiàn)在包含一個(gè)實(shí)際的 Residence 實(shí)例,而不再是 nil 。如果你試圖使用先前的可選鏈?zhǔn)秸{(diào)用訪問(wèn) numberOfRooms ,它現(xiàn)在將返回值為 1 的 Int? 類(lèi)型的值:

if let roomCount = john.residence?.numberOfRooms {
     print("John's residence has \(roomCount) room(s).")
 } else {
     print("Unable to retrieve the number of rooms.")
}
// 打印 “John's residence has 1 room(s).”
  • 強(qiáng)制解包的時(shí)機(jī)

當(dāng)你能確定你的某個(gè)值不可能是 nil 時(shí)使用 ! 進(jìn)行強(qiáng)制解包,你應(yīng)當(dāng)不希望如果它不巧意外地是 nil 的話,這句程序會(huì)直接掛掉。

總之,使用強(qiáng)制解包時(shí)一定要慎重?。?!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本章將會(huì)介紹 自動(dòng)引用計(jì)數(shù)的工作機(jī)制自動(dòng)引用計(jì)數(shù)實(shí)踐類(lèi)實(shí)例之間的循環(huán)強(qiáng)引用解決實(shí)例之間的循環(huán)強(qiáng)引用閉包引起的循環(huán)強(qiáng)...
    寒橋閱讀 1,028評(píng)論 0 0
  • 可選鏈?zhǔn)秸{(diào)用 是一種可以在當(dāng)前值可能為 nil 的可選值上請(qǐng)求和調(diào)用屬性、方法及下標(biāo)的方法。如果可選值有值,那么調(diào)...
    莽原奔馬668閱讀 383評(píng)論 0 2
  • 可選類(lèi)型 使用可選類(lèi)型(optionals)來(lái)處理值可能缺失的情況??蛇x類(lèi)型表示: 有值,等于 x 或者 沒(méi)有值,...
    蠱毒_閱讀 460評(píng)論 0 1
  • 126.析構(gòu)器 在一個(gè)類(lèi)實(shí)例銷(xiāo)毀前,一個(gè)析構(gòu)器會(huì)立即調(diào)用。使用deinit 關(guān)鍵字來(lái)表示析構(gòu)器, 跟構(gòu)造器寫(xiě)法類(lèi)似...
    無(wú)灃閱讀 902評(píng)論 0 4
  • 夜已經(jīng)深了,卻沒(méi)有睡意,可能因?yàn)殡娪爸心莻€(gè)男孩的死,可能是因?yàn)樯贁?shù)人的聲音不被聽(tīng)到的那種可悲和絕望,還好最后那群男...
    玖月的喵閱讀 72評(píng)論 0 2

友情鏈接更多精彩內(nèi)容