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?
類型
因?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ì)崩潰了:
就單從這一點(diǎn)來說躏仇,此方案能不用盡量別用恋脚,因?yàn)樵赟wift中每使用一個(gè)!
都需要慎重。如果使用!
進(jìn)行解析焰手,已讀要去掉可選包含一個(gè)非nil
的值。
1.5.3 可選綁定
如果不確定可選類型是否有值怀喉,我們通常會(huì)使用可選綁定進(jìn)行判斷:
-
if let
:如果有值书妻,則會(huì)進(jìn)入if 流程 -
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è)可選類型:
當(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ì)引起程序崩潰:
下面我們來看看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ū)別板辽,但是注釋中有這么一句話:
在 -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
所以在開發(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ù)源碼,我們可以知道皆警,??
的返回值類型有兩種拦宣,分別是T
和T?
,這點(diǎn)主要與??
后面的返回值有關(guān)信姓,也就是??
是什么類型鸵隧,返回的就是什么類型。因?yàn)椋?code>a ?? b)意推,b
也有可能是可選類型豆瘫。
此時(shí)我們可以看到c
就是個(gè)Int?
類型
此時(shí)d
是Int
類型。
如果??
后面的類型與前面的類型不匹配左痢,則會(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é)果如下:
可選鏈當(dāng)鏈上的任意一個(gè)值為nil
則不會(huì)繼續(xù)執(zhí)行后面的代碼。
2. 總結(jié)
至此我們對(duì)Swift的Optional
的分析基本就到這里了描扯,下面總結(jié)一下
-
Optional
是Swift增加的一種類型定页,用于處理值缺失的情況; - 可選表示“那有一個(gè)值绽诚,并且它等于x”或者“那兒沒有值”典徊;
- Swift中的
nil
不是指針,它是一個(gè)確定的值恩够,用來標(biāo)識(shí)值缺失卒落; - Swift中任何類型的可選狀態(tài)都可以被設(shè)置為nil,不只是對(duì)象類型蜂桶;
- 在Objective-C中儡毕,nil是一個(gè)指向不存在對(duì)象的指針。
-
Optional
的本質(zhì)是enum
扑媚,所以它具備enum
的特性腰湾,可以使用模式匹配來匹配 - 可選類型在使用的時(shí)候需要解包
- 可以使用最基本的
if
判斷是否值為nil
- 可以使用
!
進(jìn)行強(qiáng)制解包,但需要注意可能引起的崩潰問題 - 使用
if let
可選綁定疆股,著重處理有值的情況 - 使用
guard let
可選綁定费坊,守護(hù)值,著重處理沒值的情況
- 可以使用最基本的
- 對(duì)于在構(gòu)造方法中賦值后不在為
nil
的屬性旬痹,我們也可以隱式聲明可選類型附井,已解決后續(xù)總需要解包的問題讨越。 -
Optional
提供了一個(gè)與!
功能一樣的計(jì)算屬性unsafelyUnwrapped
-
unsafelyUnwrapped
在release
環(huán)境Fastest, Smallest[-Os]
優(yōu)化級(jí)別,不會(huì)執(zhí)行檢查來確保當(dāng)前私立實(shí)際上有一個(gè)值 -
Optional
提供了??
空合運(yùn)算符來便利的處理值為nil
時(shí)提供默認(rèn)值 - 對(duì)于可選鏈上的屬性或者方法的調(diào)用永毅,只要鏈上任意為
nil
則不會(huì)繼續(xù)執(zhí)行后面的代碼