Swift 可選類型Optional

Swift 可選類型Optional

[TOC]

前言

本將以Swift中的可選類型為入口,介紹:

  • 可選類型的底層實(shí)現(xiàn)
  • Swift中的nil
  • Optional的模式匹配
  • if語句以及強(qiáng)制解析
  • 可選綁定
  • 隱式解析可選類型等顷窒。

Swift中的Optional底層實(shí)現(xiàn)是enum对供,如果你對(duì)Swift中的enum不是很了解,可以先看看我的這篇文章Swift 枚舉(enum)詳解

1. Optional

1.1 簡(jiǎn)介

相對(duì)于apple開發(fā)的其他語言(C冕末、Objective-C)中,Swift增加了可選(Optional)類型,用于處理值缺失的情況凰棉。簡(jiǎn)單來說,可選就是“那有一個(gè)值陌粹,并且它等于x”或者“那沒有值”撒犀。可選有點(diǎn)像Objective-C中使用nil掏秩,但是它可以使用在任何類型上或舞,不僅僅是類∶苫茫可選類型比Objective-C中的nil指針更加安全也更具表現(xiàn)力映凳,它是Swift許多強(qiáng)大特性的重要組成部分。

雖然說可選像Objective-C中使用nil杆煞,但是C和Objective-C中并沒有可選類型的概念魏宽。nil也僅僅是針對(duì)沒有一個(gè)合法的對(duì)象的時(shí)候使用,對(duì)于結(jié)構(gòu)體决乎,基本的C類型或者枚舉類型不起作用队询。對(duì)于這些類型,Objective-C方法一般會(huì)返回一個(gè)特殊值(比如NSNotFound)來暗示缺失构诚。這種方法假設(shè)方法的調(diào)用者知道并記得對(duì)特殊值進(jìn)行判斷蚌斩。然而,Swift的可選類型可以讓你暗示任意類型的值缺失范嘱,并不需要一個(gè)特殊值送膳。

舉個(gè)例子员魏,當(dāng)我們嘗試將一個(gè)String轉(zhuǎn)換成Int:

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推測(cè)為類型 "Int?", 或者類型 "optional Int"

此時(shí)我們按住option鍵單擊convertedNumber就可以看到它是Int?類型

image

因?yàn)樯厦娴臉?gòu)造器可能會(huì)失敗叠聋,如果possibleNumber的值為Hello World撕阎,就不會(huì)構(gòu)造處一個(gè)Int,所以對(duì)于一個(gè)可選的Int應(yīng)該被寫作Int?碌补。其中這里面的?相當(dāng)于語法糖虏束,Int?等價(jià)于optional<Int>

1.2 nil

我們可以給你可選變量賦值為nil來表示它沒有值:

var serverResponseCode: Int? = 404
// serverResponseCode 包含一個(gè)可選的 Int 值 404
serverResponseCode = nil
// serverResponseCode 現(xiàn)在不包含值

但是nil不能用于非可選的常量和變量厦章。如果我們的代碼中有常量或者變量需要處理值缺失的情況镇匀,那就把它們聲明成對(duì)應(yīng)的可選類型。

如果聲明一個(gè)可選常量或者變量但沒有賦值袜啃,它們會(huì)自動(dòng)設(shè)置為nil

var surveyAnswer: String?
// surveyAnswer 被自動(dòng)設(shè)置為 nil\
print(surveyAnswer)

<!--打印結(jié)果-->
nil

Swift的nil和Objective-C中的nil并不一樣汗侵。在Objective-C中,nil是一個(gè)指向不存在對(duì)象的指針群发。在Swift中晰韵,nil不是指針,它是一個(gè)確定的值也物,用來標(biāo)識(shí)值缺失宫屠。任何類型的可選狀態(tài)都可以被設(shè)置為nil,不只是對(duì)象類型滑蚯。

1.3 Optional源碼

查看Swift源碼浪蹂,此處使用的是Swift5.3.1,可以同command+p全局搜索Optional.Swift文件告材,來快速定位坤次。由于同名文件很多,我們選擇swift-source/swift/stdlib/public/core/Optional.swift路徑下的Optional.Swift斥赋。

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
  // The compiler has special knowledge of Optional<Wrapped>, including the fact
  // that it is an `enum` with cases named `none` and `some`.

  /// 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)
  
  .....
  
}

通過源碼我們可以知道Optional的本質(zhì)使用一個(gè)具有關(guān)聯(lián)值的enum缰猴,關(guān)聯(lián)值的類型來自Optional所接收的一個(gè)泛型參數(shù)Wrapped

所以對(duì)于可選類型的寫法疤剑,以下兩種是等價(jià)的:

var a: Int? = 10
var a1: Optional<Int> = 10

1.4 Optional的模式匹配

既然Optional的本質(zhì)是枚舉滑绒,那么他也就可以使用枚舉的模式匹配來匹配對(duì)應(yīng)的值:

var a: Int? = 10

//匹配有值和沒值兩種情況
switch a {
    case .none:
        print("nil")
    case .some(let value):
        print(value)
}

// 匹配沒值,有特定值的情況
switch a {
    case .none:
        print("nil")
    case .some(10):
        print("value is 10")
    default: print("other value")
}

1.5 解包

對(duì)于可選類型隘膘,我們要想使用的時(shí)候疑故,其內(nèi)部可能有值也可能沒有值,所以我們需要對(duì)其進(jìn)行解包弯菊。

1.5.1 if判斷

我們可以使用if語句和nil比較來判斷一個(gè)可選值是否包含值纵势。可以使用==!=來比較

var a: Int? = 10

if a != nil {
    print("a has an integer value of \(a!).")
}

當(dāng)我們確定可選包含一個(gè)非nil的值,就可以使用!來強(qiáng)制解析值了钦铁。

1.5.2 強(qiáng)制解析

最簡(jiǎn)單的寫法就是強(qiáng)制解包了软舌,寫法簡(jiǎn)單,只需加一個(gè)感嘆號(hào)!牛曹,但是也有壞處佛点,就是一旦解包的值是nil,程序就會(huì)崩潰了:

image

就單從這一點(diǎn)來說躏仇,此方案能不用盡量別用恋脚,因?yàn)樵赟wift中每使用一個(gè)!都需要慎重。如果使用!進(jìn)行解析焰手,已讀要去掉可選包含一個(gè)非nil的值。

1.5.3 可選綁定

如果不確定可選類型是否有值怀喉,我們通常會(huì)使用可選綁定進(jìn)行判斷:

  1. if let:如果有值书妻,則會(huì)進(jìn)入if 流程
  2. gurad let:如果為nil,則會(huì)進(jìn)入else流程
var a: Int? = 10

if let value = a {
    print("a has an integer value of \(value).")
} else {
    print("a is nil")
}

func test() {
    guard let value = a else {
        print("a is nil")
        return
    }
    
    print("a has an integer value of \(value).")
}

test()
  • 一般我們使用if let的時(shí)候就是為了拿到可選類型的值進(jìn)行業(yè)務(wù)邏輯處理躬拢,其值只在if分支中可用躲履。
  • 我們使用guard let的時(shí)候是為了守護(hù)這個(gè)可選類型,當(dāng)可選類型沒有值聊闯,則優(yōu)先進(jìn)入else分支進(jìn)行容錯(cuò)處理工猜,如果有值,則后續(xù)都可以使用這個(gè)重綁定的值菱蔬,而不需要在進(jìn)行解包篷帅。

1.6 隱式解析可選類型

有時(shí)候在程序架構(gòu)中,第一次賦值之后拴泌,可以確定一個(gè)可選類型總會(huì)有值魏身。在這種情況下,每次都要判斷和解析可選值是非常低效的蚪腐,因?yàn)榭梢源_定它總會(huì)有值箭昵。此時(shí)我們就可以通過隱式解析來解決這個(gè)問題,通常隱式解析可選類型被用于Swift中類的構(gòu)造過程回季。

class Country {
    let name: String
    var capitalCity: String!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = capitalName
    }
}

let country = Country(name: "China", capitalName: "beijing")
let capitalName = country.capitalCity

此時(shí)當(dāng)country對(duì)象初始化后家制,其首都就已經(jīng)確認(rèn)了,在后續(xù)的使用的過程就不需要進(jìn)行解包了泡一。

但是由初始化的變量仍然是一個(gè)可選類型:

image

當(dāng)然如果在init方法中沒有給capitalCity賦值颤殴,在后續(xù)的使用的過程中與可選值為nil的時(shí)候使用!是一樣的,會(huì)觸發(fā)應(yīng)用程序的崩潰錯(cuò)誤瘾杭。所以還是那句話诅病,使用!需要謹(jǐn)慎。

我們可以把隱式解析可選類型當(dāng)做普通可選類型類判斷它是否包含值:

if country.capitalCity != nil {
    print(country.capitalCity!)
}

if let capital = country.capitalCity {
    print("The capital of \(country.name) is \(capital).")
} else {
    print("The capital of \(country.name) is nil")
}

func test() {
    guard let capital = country.capitalCity else {
        print("The capital of \(country.name) is nil")
        return
    }
    
    print("The capital of \(country.name) is \(capital).")
}

test()

注意:
如果一個(gè)變量之后可能變成nil的話請(qǐng)不要使用隱式解析可選類型。如果你需要在變量的生命周期中判斷是否是nil的話贤笆,請(qǐng)使用普通可選類型

1.7 unsafelyUnwrapped

unsafelyUnwrapped即不安全的打開蝇棉,是Swift Optional提供的一個(gè)計(jì)算屬性,提供了與強(qiáng)制解包操作符!相同的值:

var a: Int? = 10

print(a!)
print(a.unsafelyUnwrapped)

如果可選值為nil芥永,使用unsafelyUnwrapped通用會(huì)引起程序崩潰:

image

下面我們來看看unsafelyUnwrapped的源碼:

  @inlinable
  public var unsafelyUnwrapped: Wrapped {
    @inline(__always)
    get {
      if let x = self {
        return x
      }
      _debugPreconditionFailure("unsafelyUnwrapped of nil optional")
    }
  }

在源碼中我們可以看到是通過if let進(jìn)行可選綁定篡殷,然后返回值,如果綁定失敗則會(huì)打印錯(cuò)誤信息埋涧。

雖然看起來跟!沒什么區(qū)別板辽,但是注釋中有這么一句話:

image

在 -O 這個(gè)優(yōu)化級(jí)別時(shí),不會(huì)執(zhí)行檢測(cè)來確保當(dāng)前實(shí)例實(shí)際上有一個(gè)值棘催。

這里的-O是指target -> Build Setting -> Optimization Level設(shè)置成-O時(shí)劲弦。

下面我們就按照文檔上說的,將優(yōu)化級(jí)別設(shè)置成Fastest, Smallest[-Os]醇坝,并將運(yùn)行模式調(diào)整為release模式邑跪,重新運(yùn)行:

PS: 首先Debug沒生效,release也沒生效呼猪,各種優(yōu)化級(jí)別試了一遍也沒生效画畅。Fastest, Smallest[-Os] + release重啟Xcode生效了。我用的是Xcode 12.2

image

所以在開發(fā)中如果需要強(qiáng)制解包就用!吧宋距,按照官方文檔說的此屬性以安全換取性能轴踱,使用時(shí)機(jī)與!一致。

1.8 ?? 空合運(yùn)算符

對(duì)于可選值來說谚赎,其值有可能為nil淫僻,此時(shí)我們想要給其賦一個(gè)默認(rèn)值來解決值為nil的問題,如果通過解包等方法就顯得很復(fù)雜了沸版,此時(shí)使用??空合運(yùn)算符嘁傀,就會(huì)很簡(jiǎn)單,也使得代碼很簡(jiǎn)潔视粮。

空合運(yùn)算符(a ?? b)將可選類型a進(jìn)行空判斷细办,如果a包含一個(gè)值就進(jìn)行解包,否則就返回一個(gè)默認(rèn)值b蕾殴。表達(dá)式a必須是Optional類型笑撞。默認(rèn)值b的類型必須要和a存儲(chǔ)值的類型保持一致。

空合運(yùn)算符是對(duì)以下代碼的簡(jiǎn)短表達(dá)方法:

a != nil ? a! : b

以上是一個(gè)三元運(yùn)算符钓觉,當(dāng)可選值a不為空時(shí)茴肥,對(duì)其進(jìn)行強(qiáng)制解包a!以訪問a中的值;反之默認(rèn)返回b的值荡灾。如果b是一個(gè)函數(shù)瓤狐,也就不會(huì)執(zhí)行了瞬铸。

實(shí)際應(yīng)用可以是這樣:

var a: Int? = nil
print(a ?? getNum())

var b: Int? = 11
print(b ?? getNum())

func getNum() -> Int {
    print("get num")
    return 20
}

<!--打印結(jié)果-->
get num
20
11

當(dāng)b有值的時(shí)候,是不會(huì)執(zhí)行getNum函數(shù)的础锐。

下面我們看看 ?? 的實(shí)現(xiàn)嗓节,在Optional源碼中:

// 返回T
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

// 返回T?
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
    rethrows -> T? {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

根據(jù)源碼,我們可以知道皆警,??的返回值類型有兩種拦宣,分別是TT?,這點(diǎn)主要與??后面的返回值有關(guān)信姓,也就是??是什么類型鸵隧,返回的就是什么類型。因?yàn)椋?code>a ?? b)意推,b也有可能是可選類型豆瘫。

image

此時(shí)我們可以看到c就是個(gè)Int?類型

image

此時(shí)dInt類型。

image

如果??后面的類型與前面的類型不匹配左痢,則會(huì)報(bào)編譯錯(cuò)誤靡羡。Cannot convert value of type 'String' to expected argument type 'Int'無法將類型'String'的值轉(zhuǎn)換為期望的參數(shù)類型'Int'。

1.9 可選鏈

可選鏈的意思就是允許在一個(gè)鏈上來訪問當(dāng)前屬性/方法俊性,示例:

class Person {
    var name: String?
    var subject: Subject?
}

class Subject {
    var subjectName: String?
    func test(){print("test")}
}

var s = Subject()
var p = Person()

if let sName = p.subject?.subjectName {
    print("subjectName is \(sName)")
} else {
    print("subjectName is nil")
}

p.subject?.test()

運(yùn)行結(jié)果如下:


image

可選鏈當(dāng)鏈上的任意一個(gè)值為nil則不會(huì)繼續(xù)執(zhí)行后面的代碼。

2. 總結(jié)

至此我們對(duì)Swift的Optional的分析基本就到這里了描扯,下面總結(jié)一下

  1. Optional是Swift增加的一種類型定页,用于處理值缺失的情況;
  2. 可選表示“那有一個(gè)值绽诚,并且它等于x”或者“那兒沒有值”典徊;
  3. Swift中的nil不是指針,它是一個(gè)確定的值恩够,用來標(biāo)識(shí)值缺失卒落;
  4. Swift中任何類型的可選狀態(tài)都可以被設(shè)置為nil,不只是對(duì)象類型蜂桶;
  5. 在Objective-C中儡毕,nil是一個(gè)指向不存在對(duì)象的指針。
  6. Optional的本質(zhì)是enum扑媚,所以它具備enum的特性腰湾,可以使用模式匹配來匹配
  7. 可選類型在使用的時(shí)候需要解包
    1. 可以使用最基本的if判斷是否值為nil
    2. 可以使用!進(jìn)行強(qiáng)制解包,但需要注意可能引起的崩潰問題
    3. 使用if let可選綁定疆股,著重處理有值的情況
    4. 使用guard let可選綁定费坊,守護(hù)值,著重處理沒值的情況
  8. 對(duì)于在構(gòu)造方法中賦值后不在為nil的屬性旬痹,我們也可以隱式聲明可選類型附井,已解決后續(xù)總需要解包的問題讨越。
  9. Optional提供了一個(gè)與!功能一樣的計(jì)算屬性unsafelyUnwrapped
  10. unsafelyUnwrappedrelease環(huán)境Fastest, Smallest[-Os]優(yōu)化級(jí)別,不會(huì)執(zhí)行檢查來確保當(dāng)前私立實(shí)際上有一個(gè)值
  11. Optional提供了??空合運(yùn)算符來便利的處理值為nil時(shí)提供默認(rèn)值
  12. 對(duì)于可選鏈上的屬性或者方法的調(diào)用永毅,只要鏈上任意為nil則不會(huì)繼續(xù)執(zhí)行后面的代碼
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末把跨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子卷雕,更是在濱河造成了極大的恐慌节猿,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漫雕,死亡現(xiàn)場(chǎng)離奇詭異滨嘱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浸间,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門太雨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魁蒜,你說我怎么就攤上這事囊扳。” “怎么了兜看?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵锥咸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我细移,道長(zhǎng)搏予,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任弧轧,我火速辦了婚禮雪侥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘精绎。我一直安慰自己速缨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布代乃。 她就那樣靜靜地躺著旬牲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪襟己。 梳的紋絲不亂的頭發(fā)上引谜,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音擎浴,去河邊找鬼员咽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贮预,可吹牛的內(nèi)容都是我干的贝室。 我是一名探鬼主播契讲,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼滑频!你這毒婦竟也來了捡偏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤峡迷,失蹤者是張志新(化名)和其女友劉穎银伟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绘搞,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡彤避,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夯辖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琉预。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蒿褂,靈堂內(nèi)的尸體忽然破棺而出圆米,到底是詐尸還是另有隱情,我是刑警寧澤啄栓,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布娄帖,位于F島的核電站,受9級(jí)特大地震影響昙楚,放射性物質(zhì)發(fā)生泄漏块茁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一桂肌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧永淌,春花似錦崎场、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至李滴,卻和暖如春螃宙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背所坯。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工谆扎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芹助。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓堂湖,卻偏偏與公主長(zhǎng)得像闲先,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子无蜂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容