Swift可選類(lèi)型知識(shí)梳理

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)一樣。

最后編輯于
?著作權(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)容

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