Swift中加入了可選類(lèi)型Optional,用來(lái)處理值可能缺失的情況??蛇x類(lèi)型表示兩種可能: 或者有值, 你可以解析并訪問(wèn)這個(gè)值, 或者根本沒(méi)有值;
可選類(lèi)型的作用
- 處理哨崗值問(wèn)題
Optional的存在是很有必要的,大部分語(yǔ)言是沒(méi)有這種類(lèi)型的,它們表示值可能缺失的情況就不太方便了;比如說(shuō)一個(gè)Int類(lèi)型的屬性,如何表示這個(gè)屬性未賦值過(guò)或者說(shuō)缺失為空的情況呢;Int這類(lèi)基本類(lèi)型并不能像其他對(duì)象一樣通過(guò)nil(null)來(lái)表示缺失情況,同樣也不好使用默認(rèn)值0來(lái)表示,那么一般的處理方式就是設(shè)置一個(gè)哨崗值來(lái)表示:我們可以約定這個(gè)Int類(lèi)型的屬性默認(rèn)/缺失值為-1,這個(gè)-1就是哨崗值;OC中的NSNotFound其實(shí)也是類(lèi)似的哨崗值,只不過(guò)這個(gè)值是個(gè)比較大的數(shù);
static const NSInteger NSNotFound = NSIntegerMax;
#define NSIntegerMax LONG_MAX
不過(guò)這種策略是有問(wèn)題的,因?yàn)檫@個(gè)哨崗值不管從哪個(gè)角度看都是一個(gè)真實(shí)值;你也可能會(huì)忘記檢查這個(gè)值而不小心使用了它;而可選類(lèi)型很好的處理了這種情況;
- 類(lèi)型安全
在OC中,對(duì)nil發(fā)送消息是安全的,但在Swift中是有問(wèn)題的;編寫(xiě)代碼時(shí),開(kāi)發(fā)者都不能明確某個(gè)對(duì)象在某個(gè)時(shí)刻會(huì)變?yōu)閚il;如果再去使用這個(gè)對(duì)象,就可能會(huì)造成異常;有了可選類(lèi)型,開(kāi)發(fā)者在使用對(duì)象時(shí)就必須去解析這個(gè)值并判斷為nil的情況;
可選類(lèi)型的本質(zhì)
Optional結(jié)構(gòu)如下:
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
....
}
- 可以看到,它本質(zhì)是一個(gè)
enum類(lèi)型,而且是一個(gè)關(guān)聯(lián)值的enum;這個(gè)枚舉只有兩個(gè)值,表示缺少值的none,表示具體關(guān)聯(lián)值的some(Wrapped),Wrapped就是具體值;
獲取關(guān)聯(lián)值的唯一方法就是使用switch或者if case語(yǔ)句;和哨崗值不同,除非通過(guò)解包取出關(guān)聯(lián)值,否則是不可能意外的得到這個(gè)值; - 另外
Optional<Wrapped>這里也是一個(gè)泛型,原則上任何類(lèi)型都有可選類(lèi)型; - Optional 同時(shí)也遵循了
ExpressibleByNilLiteral協(xié)議,ExpressibleByNilLiteral這個(gè)協(xié)議就是nil字面量協(xié)議,類(lèi)似的還有字符串的字面量協(xié)議ExpressibleByStringLiteral,數(shù)組字面量協(xié)議ExpressibleByArrayLiteral;這也是case none就能以字面量nil表示的原因;
我們可以這樣聲明一個(gè)可選整型:
var opValue: Optional<Int> = Optional.init(1)
// or
// var opValue: Optional<Int> = 1
這個(gè)聲明可以用?語(yǔ)法糖簡(jiǎn)化表示:
var opValue: Int? = 1
使用switch解包得到關(guān)聯(lián)值:
switch opValue {
case .none:
print("nil")
case .some(let value):
print(value)
}
或使用if case語(yǔ)句:
if case Optional.some(let value) = opValue {
print(value)
}else {
print("nil")
}
因?yàn)樽裱俗置媪繀f(xié)議上面的解包可以替換為:
switch opValue {
case nil:
print("nil")
case .some(let value):
print(value)
}
Swift 的 nil 和 Objective-C 中的 nil 并不一樣。在 Objective-C 中,nil 是一個(gè)指向不存在對(duì)象的指針。在 Swift 中,nil 不是指針——它是一個(gè)確定的值,用來(lái)表示值缺失。任何類(lèi)型的可選狀態(tài)都可以被設(shè)置為 nil,不只是對(duì)象類(lèi)型。
強(qiáng)制解析
可以使用 if 語(yǔ)句和 nil 比較來(lái)判斷一個(gè)可選值是否包含值;
當(dāng)你確定可選類(lèi)型確實(shí)包含值之后,你可以在可選的名字后面加一個(gè)感嘆號(hào)!來(lái)獲取值。這個(gè)驚嘆號(hào)表示“我知道這個(gè)可選有值,請(qǐng)使用它”,這就是可選值的強(qiáng)制解析;
if opValue != nil {
print(opValue!)
}else {
print("nil")
}
使用 ! 來(lái)獲取一個(gè)不存在的可選值會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。使用 ! 來(lái)強(qiáng)制解析值之前,一定要確定可選包含一個(gè)非 nil 的值。
可選綁定
- if let
可選類(lèi)型很常用,很多時(shí)候都需要使用上面的switch,if case解析;這種解析的確有點(diǎn)冗余;不過(guò)使用if let進(jìn)行可選綁定(optional binding)來(lái)判斷可選類(lèi)型是否包含值,如果包含就把值賦給一個(gè)臨時(shí)常量或者變量,可以更簡(jiǎn)單的解析;
上面例子使用可選綁定的代碼:
if let value = opValue {
print(value)
}
也可以在同一個(gè)if語(yǔ)句中綁定多個(gè)值,當(dāng)多個(gè)可選值都有解析值時(shí)條件成立:
只有opValue1,opValue2都有值時(shí)才會(huì)調(diào)用輸出結(jié)果:
let opValue1: Int? = 1
let opValue2: Int? = nil
if let v1 = opValue1, let v2 = opValue2 {
print(v1,v2)
}
- guard let
通常使用if let判斷有值之后,會(huì)做具體的邏輯實(shí)現(xiàn),通常代碼多,而且也多了一層分支;guard let和if let剛好相反,guard let守護(hù)一定有值。如果沒(méi)有,直接返回;guard let 在一定程度上能使代碼更加清晰易讀:
guard let v = opValue else { return }
print(v)
一個(gè)比較明顯的例子:
if let v1 = opValue1 {
if let v2 = opValue2 {
if let v3 = opValue3 {
print(v1,v2,v3)
}
}
}
guard let v1 = opValue1 else { return }
guard let v2 = opValue2 else { return }
guard let v3 = opValue3 else { return }
print(v1,v2,v3)
guard let同樣可以綁定多個(gè)值:
guard let v1 = opValue1,let v2 = opValue2,let v3 = opValue3 else { return }
print(v1,v2,v3)
在
if條件語(yǔ)句中使用常量和變量來(lái)創(chuàng)建一個(gè)可選綁定,僅在if語(yǔ)句的句中(body)中才能獲取到值。相反,在guard語(yǔ)句中使用常量和變量來(lái)創(chuàng)建一個(gè)可選綁定,僅在guard語(yǔ)句外且在語(yǔ)句后才能獲取到值;
另外,guard并不局限于綁定上,它也能夠接受任何在if語(yǔ)句中能接受的條件;附:使用 guard 的正確姿勢(shì)
可選鏈
可選鏈Optional Chaining:多個(gè)連續(xù)的調(diào)用可以被鏈接在一起形成一個(gè)調(diào)用鏈,如果其中任何一個(gè)節(jié)點(diǎn)為空(nil)將導(dǎo)致整個(gè)鏈調(diào)用失敗。也就是說(shuō)可以將可選值的調(diào)用鏈接起來(lái);
let str: String? = ""
str?.uppercased().lowercased()
- 在OC中,對(duì)nil發(fā)送消息什么都不會(huì)發(fā)生;在Swift中,可以通過(guò)可選鏈來(lái)達(dá)到同樣的效果;
delegate?.callback()
和OC不同的是,如果沒(méi)有值,那么這里的問(wèn)號(hào)對(duì)于代碼的閱讀者來(lái)說(shuō)是一個(gè)清晰的信號(hào),表示方法可能不會(huì)調(diào)用;
- 當(dāng)調(diào)用可選鏈得到一個(gè)返回值時(shí),這個(gè)返回值也是可選類(lèi)型;因?yàn)榭蛇x鏈的結(jié)果可能為nil;
- 可以使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制解析,使用可選鏈當(dāng)可選值為空時(shí)調(diào)用只會(huì)調(diào)用失敗,然而強(qiáng)制解析將會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。
- 可選鏈簡(jiǎn)化判斷:
var str: String? = ""
if str != nil {
str = str?.lowercased()
if str != nil {
str = str?.uppercased()
print(str)
}
}
if let result = str?.lowercased().uppercased() {
print(result)
}
隱式解析
有時(shí)候在程序架構(gòu)中,第一次被賦值之后,可以確定一個(gè)可選類(lèi)型總會(huì)有值。在這種情況下,每次都要判斷和解析可選值是非常低效的,因?yàn)榭梢源_定它總會(huì)有值。這種情況我們就可以定義隱式解析可選類(lèi)型(implicitly unwrapped optionals)。把想要用作可選的類(lèi)型的后面的問(wèn)號(hào)(String?)改成感嘆號(hào)(String!)來(lái)聲明一個(gè)隱式解析可選類(lèi)型。
let opValue: Int! = 1
print(opValue + 1)
如果你在隱式解析可選類(lèi)型沒(méi)有值的時(shí)候嘗試取值,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。和你在沒(méi)有值的普通可選類(lèi)型后面加一個(gè)驚嘆號(hào)一樣。