在編程世界中有一種非常通用的模式,那就是某個(gè)操作是否要返回一個(gè)有效值。
在 Objective-C
中挟憔,對(duì) nil
發(fā)送消息是安全的。如果這個(gè)消息簽名返回一個(gè)對(duì)象烟号,那么
nil
會(huì)被返回绊谭;如果消息返回的是一個(gè)結(jié)構(gòu)體那么它的值都將為零。但在實(shí)際開發(fā)中我們常常會(huì)碰到由于對(duì)象為 nil
而導(dǎo)致的崩潰或者不想出現(xiàn)的問題汪拥,比如數(shù)組达传、字典、block的判空處理等等喷楣。
Tony Hoare在1965年設(shè)計(jì)了 null
引用趟大,他對(duì)此設(shè)計(jì)表示痛心疾首,并將這個(gè)問題稱為“價(jià)值10億美元的錯(cuò)誤”:
那時(shí)候铣焊,我正在為一門面向?qū)ο蟮恼Z言(ALGOL W)設(shè)計(jì)第一個(gè)全面的引用類型系統(tǒng)逊朽。我的目標(biāo)是在編譯器自動(dòng)執(zhí)行的檢查的保證下,確保對(duì)于引用的所有使用都是安全的曲伊。但是我沒能抵擋住
null
引用的誘惑叽讳,因?yàn)樗菀讓?shí)現(xiàn)了追他。這導(dǎo)致了不計(jì)其數(shù)的錯(cuò)誤,漏洞以及系統(tǒng)崩潰岛蚤。這個(gè)問題可能在過去四十年里造成了有十億美元的損失邑狸。
前面敘述這么多是為了引出編程語言中更安全的設(shè)計(jì) - swift中的可選類型
swift定義后綴 ?
來作為標(biāo)準(zhǔn)庫中的定義的命名型類型 Optional<Wrapped>
的語法糖。換句話說涤妒,下面兩個(gè) 聲明是等價(jià)的:
var optionalInteger: Int?
var optionalInteger: Optional<Int>
在上述兩種情況下单雾,變量 optionalInteger
都被聲明為可選整型類型。注意在類型和 ?
之間沒有空格她紫。
類型 Optional<Wrapped>
是一個(gè)枚舉硅堆,有兩個(gè)成員,none
和 some(Wrapped)
贿讹,用來表示可能有也可能沒有的值渐逃。任意類型都可以被顯式地聲明(或隱式地轉(zhuǎn)換)為可選類型。如果你在聲明或定義可選變量或?qū)傩缘臅r(shí)候沒有提供初始值民褂,它的值則會(huì)自動(dòng)賦為默認(rèn)值 nil
茄菊。
如果一個(gè)可選類型的實(shí)例包含一個(gè)值,那么你就可以使用后綴運(yùn)算符 !
來獲取該值(即強(qiáng)制解包)赊堪,正如下面描述的:
optionalInteger = 42
optionalInteger! // 42
使用 !
運(yùn)算符解包值為 nil
的可選值會(huì)導(dǎo)致運(yùn)行錯(cuò)誤面殖。
你也可以使用可選鏈?zhǔn)秸{(diào)用和可選綁定來選擇性地在可選表達(dá)式上執(zhí)行操作。如果值為 nil
雹食,不會(huì)執(zhí)行任何操作畜普,因此也就沒有運(yùn)行錯(cuò)誤產(chǎn)生。
- 處理可選值值缺失的方法
一個(gè)可選的值是一個(gè)具體的值或者是 nil
以表示值缺失群叶。在類型后面加一個(gè)問號(hào)來標(biāo)記這個(gè)變量的值是可選的吃挑。你可以一起使用 if
和 let
來處理值缺失的情況:
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ì)被跳過街立。如果不是 nil
舶衬,會(huì)將值解包并賦給 let
后面的常量 name
,這樣代碼塊中就可以使用這個(gè)值了赎离。
還有另外一種處理值缺失的方法逛犹,使用 ??
操作符來提供一個(gè)默認(rèn)值。如果可選值缺失的話梁剔,可以使用默認(rèn)值來代替虽画。
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
因?yàn)?nickName
為 nil
,所以 informalGreeting
的值就變成了 "Hi John Appleseed"
荣病。
- 隱式解析可選類型
當(dāng)可以被訪問時(shí)码撰,Swift 語言定義后綴 !
作為標(biāo)準(zhǔn)庫中命名類型 Optional<Wrapped>
的語法糖,來實(shí)現(xiàn)自動(dòng) 解包的功能个盆。換句話說脖岛,下面兩個(gè)聲明等價(jià):
var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>
由于隱式解包修改了包涵其類型的聲明語義朵栖,嵌套在元組類型或泛型的可選類型(比如字典元素類型或數(shù)組元素
類型),不能被標(biāo)記為隱式解包柴梆。例如:
let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 錯(cuò)誤
let implicitlyUnwrappedTuple: (Int, Int)! // 正確
let arrayOfImplicitlyUnwrappedElements: [Int!] // 錯(cuò)誤
let implicitlyUnwrappedArray: [Int]! // 正確
由于隱式解析可選類型和可選類型有同樣的表達(dá)式析可選類型陨溅。比如,你可以將隱式解析可選類型的值賦給變量绍在、常量和可選屬性门扇,反之亦然。
正如可選類型一樣揣苏,你在聲明隱式解析可選類型的變量或?qū)傩缘臅r(shí)候也不用指定初始值悯嗓,因?yàn)樗心J(rèn)值 nil
。
可以使用可選鏈?zhǔn)秸{(diào)用來在隱式解析可選表達(dá)式上選擇性地執(zhí)行操作卸察。如果值為 nil
,就不會(huì)執(zhí)行任何操作铅祸,因此也不會(huì)產(chǎn)生運(yùn)行錯(cuò)誤坑质。
- 使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制展開
通過在想調(diào)用的屬性、方法临梗、或下標(biāo)的可選值后面放一個(gè)問號(hào) ?
涡扼,可以定義一個(gè)可選鏈。這一點(diǎn)很像在可選 值后面放一個(gè)嘆號(hào) !
來強(qiáng)制展開它的值盟庞。它們的主要區(qū)別在于當(dāng)可選值為空時(shí)可選鏈?zhǔn)秸{(diào)用只會(huì)調(diào)用失敗吃沪,然而強(qiáng)制展開將會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。
為了反映可選鏈?zhǔn)秸{(diào)用可以在空值 nil
上調(diào)用的事實(shí)什猖,不論這個(gè)調(diào)用的屬性票彪、方法及下標(biāo)返回的值是不是可選值,它的返回結(jié)果都是一個(gè)可選值不狮。你可以利用這個(gè)返回值來判斷你的可選鏈?zhǔn)秸{(diào)用是否調(diào)用成功降铸,如果調(diào)用 有返回值則說明調(diào)用成功,返回 nil
則說明調(diào)用失敗摇零。
特別地推掸,可選鏈?zhǔn)秸{(diào)用的返回結(jié)果與原本的返回結(jié)果具有相同的類型,但是被包裝成了一個(gè)可選值驻仅。例如谅畅,使用 可選鏈?zhǔn)秸{(diào)用訪問屬性,當(dāng)可選鏈?zhǔn)秸{(diào)用成功時(shí)噪服,如果屬性原本的返回結(jié)果是 Int
類型毡泻,則會(huì)變?yōu)?Int?
類型。
代碼示例芯咧,首先定義兩個(gè)類 Person
和 Residence
:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
Residence
有一個(gè) Int
類型的屬性 numberOfRooms
牙捉,其默認(rèn)值為 1 竹揍。 Person
具有一個(gè)可選的 residence
屬性,其類型為 Residence?
邪铲。
假如你創(chuàng)建了一個(gè)新的 Person
實(shí)例,它的 residence
屬性由于是是可選型而將初始化為 nil
,在下面的代碼中, john
有一個(gè)值為 nil
的 residence
屬性:
let john = Person()
如果使用嘆號(hào) !
強(qiáng)制展開獲得這個(gè) john
的 residence
屬性中的 numberOfRooms
值芬位,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤,因?yàn)檫@時(shí) residence
沒有可以展開的值:
let roomCount = john.residence!.numberOfRooms // 這會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤
john.residence
為非 nil
值的時(shí)候带到,上面的調(diào)用會(huì)成功昧碉,并且把 roomCount
設(shè)置為 Int
類型的房間數(shù)量。正如上面提到的揽惹,當(dāng) residence
為 nil
的時(shí)候上面這段代碼會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤被饿。
可選鏈?zhǔn)秸{(diào)用提供了另一種訪問 numberOfRooms
的方式,使用問號(hào) ?
來替代原來的嘆號(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
后面添加問號(hào)之后搪搏,Swift
就會(huì)在 residence
不為 nil
的情況下訪問 numberOfRooms
狭握。
因?yàn)樵L問 numberOfRooms
有可能失敗,可選鏈?zhǔn)秸{(diào)用會(huì)返回 Int?
類型疯溺,或稱為“可選的 Int
”论颅。如上例所 示,當(dāng) residence
為 nil
的時(shí)候囱嫩,可選的 Int
將會(huì)為 nil
恃疯,表明無法訪問 numberOfRooms
。訪問成功時(shí)墨闲,可選的
Int
值會(huì)通過可選綁定展開今妄,并賦值給非可選類型的 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)用訪問 numberOfRooms
攒砖,它現(xiàn)在將返回值為 1 的 Int?
類型的值:
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í)一定要慎重0悍鳌J苌瘛!