枚舉及可選型是 Swift 中兩個很重要的概念兼蕊,前者與 Objective-C 中的概念大不相同洛口,后者完全不存在慌烧,因此需要詳細介紹下這兩個概念炫乓。
為什么將可選型與枚舉放在一起呢?因為可選型 Optional 本質就是枚舉 Emun闸衫,接下來詳細介紹涛贯。
枚舉(Enum)
Enum 基本語法
Swift 中枚舉的語法非常簡潔:
// 定義一個表示季節(jié)的枚舉
enum Season {
case spring // 春
case summer // 夏
case autumn // 秋
case winter // 冬
}
// 創(chuàng)建一個 Season 實例變量
var s = Season.spring
s = Season.summer // 重新賦值
s = .winter // 在確定 s 的類型為 Season 后可以直接使用.語法
print("\(s) is coming!") // winter is coming
// 在 switch 語句中使用 Enum
switch s {
case .spring:
print("春天")
case .summer:
print("夏天")
case .autumn:
print("秋天")
case .winter:
print("冬天")
}
如果讓枚舉遵循 CaseIterable 協(xié)議,即可獲得一個 allCases 屬性蔚出,用于表示一個包含枚舉所有成員的集合:
// 枚舉成員的遍歷
enum Season: CaseIterable {
case spring, summer, autumn, winter
}
// 通過 allCases 屬性獲得枚舉成員的數(shù)量
let seasonNumber = Season.allCases.count
// 通過 allCases 屬性在 for 循環(huán)中遍歷枚舉成員
for s in Season.allCases {
print("Current season is \(s)")
}
關聯(lián)值(Associated Values)
// 定義一個日期枚舉弟翘,有兩種 case : 1.數(shù)字表示的日期 2.字符串表示的日期
enum Date {
case digit(year: Int, month: Int, dat: Int)
case string(String)
}
var digitDate = Date.digit(year: 2019, month: 8, dat: 28)
var stringDate = Date.string("2019-08-28")
需要注意: 關聯(lián)值是存儲在枚舉變量的內存里 ,比如上面的 2019 骄酗、8 稀余、28 、"2019-08-28" 都是存儲在變量 digitDate 內
原始值(Raw Values)
可以指定 Enum 各個 case 的原始值類型
// 枚舉 PokerSuit 原始值的類型為 Character
enum PokerSuit: Character {
case spade = "?"
case heart = "?"
case diamond = "?"
case club = "?"
}
let suit = PokerSuit.heart
print(suit) // heart
print(suit.rawValue) // ?
需要注意: 原始值不存儲在枚舉變量的內存里 趋翻,比如下面的枚舉:
enum Level: String {
case low = "low level"
case middle = "middle level"
case high = "high level"
}
print(MemoryLayout<Level>.stride) // 1
print(MemoryLayout<Level>.size) // 1
print(MemoryLayout<Level>.alignment) // 1
隱式原始值(Implicitly Assigned Raw Values)
如果指定的類型為 String 或者 Int睛琳,Swift 編譯器會隱式(自動)添加原始值:
enum Direction : String {
case north
case south
case east
case west
}
// 完全等價于
enum Direction : String {
case north = "north"
case south = "south"
case east = "east"
case west = "west"
}
enum Season : Int {
case spring
case summer
case autumn
case winter
}
// 也完全等價于
enum Season : Int {
case spring = 0
case summer = 1
case autumn = 2
case winter = 3
}
遞歸枚舉(Recursive Enumeration)
遞歸枚舉是一種特殊的枚舉類型,它有一個或多個枚舉成員使用該枚舉類型的實例作為關聯(lián)值踏烙。使用遞歸枚舉時师骗,編譯器會插入一個間接層。你可以在枚舉成員前加上 indirect 來表示該成員可遞歸宙帝。
例如丧凤,下面的例子中,枚舉類型存儲了簡單的算術表達式:
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
也可以在枚舉類型開頭加上 indirect 關鍵字來表明它的所有成員都是可遞歸的:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
可選型(Optional)
Swift 標準庫源碼 Optional 的定義:
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)
...
}
很明顯 Optional 就是一個 enum 類型步脓,且有兩個 case :none 、 some
其中 some 的關聯(lián)值為泛型 < Wrapped >浩螺,即 Optional 內存儲的數(shù)據(jù)類型為 Wrapped
從定義中不難理解可選型表示 一個類型即可能有值又可能為 nil 靴患。
基本概念
定義一個普通的 String 類型變量:
var str: String = "hello"
然后將 str 變量清空:
str = nil
編譯器會立馬報錯:
'nil' cannot be assigned to type 'String'
說明 Swift 不允許普通類型的變量賦值為 nil,要想 即可能有值又可能為空 要出,需要使用可選類型 Optional鸳君,其基本語法如下:
var name: String? // 默認為 nil
name = "CodingIran"
name = nil
var age: Int? // 默認值為 nil
age = 30
可選型不僅可以表示這個類型的值可能為 nil,而且一旦聲明為可選型之后患蹂, 其默認值即為 nil 或颊。
另外需要注意:任何類型(如:Int砸紊、Double、Struct 等)的可選狀態(tài)都可以被設置為 nil囱挑,不只是對象類型醉顽!
可選型的概念有點類似薛定諤的貓,可以將其想象成一個盒子平挑,其中可能裝著某個具體類型的數(shù)據(jù)游添,也有可能什么都沒有。那如何才能打開盒子呢通熄?
強制解包(Forced Unwrapping)
Swift 提供如下語法可以強制打開一個可選類型:
// 定一個可選 Int 類型的變量唆涝,表示網絡請求的返回狀態(tài)碼
var responseCode: Int? = 404
print(responseCode!) // 404
responseCode = nil
print(responseCode!) // Fatal error: Unexpectedly found nil while unwrapping an Optional value
很顯然是強制解包是不安全的, 除非能夠確保一個可選類型一定有值 唇辨, 否則不要輕易嘗試強制解包 廊酣!在使用可選類型的值時應該先進行判斷:
// 判斷可選型是否為空
if responseCode != nil {
print("responseCode's value is \(responseCode!)")
} else {
print("responseCode has no value!")
}
這么寫雖然解決了 nil 導致的錯誤,但顯得很笨拙赏枚,對此 Swift 提供了一些更優(yōu)雅的語法亡驰。
可選綁定(Optional Binding)
使用可選綁定來判斷可選類型是否包含值,如果包含就把值賦給一個臨時常量或者變量嗡贺。
可選綁定可以用在 if 和 while 語句中隐解,這條語句不僅可以用來判斷可選類型中是否有值,同時可以將可選類型中的值賦給一個常量或者變量:
// 創(chuàng)建 unwrappedResponseCode 常量用來接受 responseCode 解包成功后結果
if let unwrappedResponseCode = responseCode {
print("responseCode's value is \(unwrappedResponseCode)")
} else {
// 解包失敗(nil)會進入 else 分支
print("responseCode has no value!")
}
這段代碼可以理解為:
如果 responseCode 返回的可選 Int 包含一個值诫睬,則創(chuàng)建一個叫做 unwrappedResponseCode 的新常量并將可選包含的值賦給它煞茫。
如果解包成功,unwrappedResponseCode 常量可以在 if 語句的第一個分支中使用摄凡。它已經被可選類型包含的值初始化過续徽,所以不需要再使用 ! 后綴來獲取它的值。
-
可以在可選綁定中使用常量或變量亲澡。如果想在 if 語句的第一個分支中操作 unwrappedResponseCode 的值钦扭,可以改成
if var unwrappedResponseCode...
憑空冒出一個新的變量(or 常量)名unwrappedResponseCode 顯得有些奇怪,所以通常都讓創(chuàng)建出的新名稱與可選型的名稱保持一致:
// 等號左右的名稱一樣
if let responseCode = responseCode {
print("responseCode's value is \(responseCode)")
} else {
print("responseCode has no value!")
}
第一次看到if let responseCode = responseCode
可能會覺得『臥槽床绪,還可以這樣客情?』。
別急癞己,你甚至還可以這樣:
var responseCode: Int?
var responseData: Dictionary<String, Any>?
// do someting...
if let responseCode = responseCode, // 可選綁定 responseCode
let responseData = responseData, // 可選綁定 responseData
responseCode != 404, // 判斷 responseCode
responseData.count != 0 { // 判斷 responseData
// do someting...
}
guard 語句
在我們平時寫代碼的時候經常會遇到類型下面的代碼:
/// 定義一個計算形狀邊數(shù)的函數(shù)
///
/// - Parameter shape: 形狀字符串
/// - Returns: 形狀的變數(shù)(由于傳入的 shape 位置可能導致返回 nil膀斋,因此返回的是可選 Int)
func calculateNumberOfSides(shape: String) -> Int? {
switch shape {
case "Triangle": // △
return 3
case "Square": // □
return 4
case "Rectangle": // ?
return 4
case "Pentagon": // ?
return 5
case "Hexagon":
return 6 // ?
default:
return nil
}
}
func maybePrintSides(shape: String) {
let sides = calculateNumberOfSides(shape: shape)
if let sides = sides {
print("A \(shape) has \(sides) sides.")
} else {
print("I don't know the number of sides for \(shape).")
}
}
上面的代碼沒毛病,在 oc 時代我們幾乎每天都在寫這樣的 if else 語句痹雅,但 Swift 給了一種新的條件判斷的語句 guard:
func maybePrintSides(shape: String) {
guard let sides = calculateNumberOfSides(shape: shape) else {
print("I don't know the number of sides for \(shape).")
return
}
print("A \(shape) has \(sides) sides.")
}
有沒有發(fā)現(xiàn)使用 guard 有什么好處仰担?
當使用 guard 語句進行可選項綁定時,綁定的常量(let)绩社、變量(var)也能 在外層作用域中使用 摔蓝!
隱式解包(Implicitly Unwrapped Optional)
可選型的引入對于程序安全性有很大的提升赂苗,但即使使用可選綁定,有時依然覺得有些繁瑣:
let number: Int? = 10
if let number = number {
print("number is \(number)")
} else {
print("number is non-existent!")
}
上面這段代碼中定義了一個 Int 可選型的常量 number贮尉,并將 10 賦值給它拌滋。
既然是常量,那 number 永遠都是 10绘盟,不可能出現(xiàn) nil 的情況鸠真,這時候再去擔心就顯得很多余。對此 Swift 推出了隱式解包的概念:
let number: Int! = 10
let otherNumber: Int = number
print("otherNumber is \(otherNumber)")
需要注意:number 變量依然是一個可選 Int龄毡,只是在將它賦值給 otherNumber 時會 自動強制解包 7途怼(因為你已經確保 number 不會為 nil 了)
空合并運算符(Nil Coalescing)
在 Swift 中還有一個更加方便的方法可以對可選類型進行解包。如果想獲取一個可選類型的值沦零,并且希望在它為空時 給它一個默認值 的話祭隔,空合并運算符是最好的方法:
var optionalInt: Int? = 10
var mustHaveResult = optionalInt ?? 0
// mustHaveResult 類型為 Int,值為 10
上面的代碼optionalInt ?? 0
表示如果 optionalInt
有值路操,則返回自動解包的 optionalInt 值 10疾渴,如果為空則返回 0,其完全等價于下面的代碼:
var optionalInt: Int? = 10
var mustHaveResult: Int
if let unwrapped = optionalInt {
mustHaveResult = unwrapped
} else {
mustHaveResult = 0
}
再看一種情況:
var a: Int? = 1
var b: Int? = 2
let c = a ?? b
此時 c 是什么類型屯仗?值又是多少搞坝?
普通青年這時候會開始瞎幾把猜了,文藝青年已經打開 Xcode 進行實戰(zhàn)操作魁袜,二逼青年直接去看 Swift 定義 ?? 運算符的源碼桩撮。沒錯!我就是二逼青年:
// 可選型 ?? 非可選型
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
rethrows -> T {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
// 可選型 ?? 可選型
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
rethrows -> T? {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
上面就是 ?? 的實現(xiàn)源碼峰弹,可以在 Swift 源碼文件夾(swift->stdlib->public->core->Optional.swift)
查看店量。簡簡單單的10幾行代碼中有2個關鍵:
空合并運算符 ?? 有兩個函數(shù),對比發(fā)現(xiàn)區(qū)別在于自動閉包內的返回值類型如果為 T 鞠呈,整個 ?? 函數(shù)返回 T融师,如果自動閉包內的返回值類型如果為 T? ,整個 ?? 函數(shù)也返回 T?
?? 函數(shù)體內使用 switch 語句對 optional 做了判斷(如前面所說蚁吝, 可選型的本質是枚舉 ):如果有值則返回這個值旱爆,如果為空則返回默認值 defaultValue
因此對于 a ?? b
可以總結如下:
- a 是必須是可選項(見源碼函數(shù)的第一個參數(shù))
- b 可以是可選項也可以不是(源碼的兩個函數(shù)的第二個參數(shù)自動閉包的返回值類型,一個是可選型窘茁,另一個是非可選型)
- a 和 b 儲存的類型必須相同(源碼唯一的泛型 T )
- 如果 a 不為 nil疼鸟,就返回 a(源碼第一個 case 語句)
- 如果 a 為 nil,就返回 b(源碼第二個case 語句)
- 如果 b 不是可選項庙曙,返回 a 時會自動解包(源碼兩個函數(shù) ?? 函數(shù)的返回值一個為 T,另一個為 T?)