可選項,一般也叫可選類型,它允許將值設為nil
。
一、定義可選項
平時開發中,如果我們需要把一個變量置空時只需要把變量賦值一個nil
即可:
上面嘗試后不行,那怎么把一個變量置空呢?
答案:把變量設置可選類型即可
如何定義可選類型(可選項)?
- 在類型后面加個問號
?
; - 定義可選項后變量默認就是
nil
。
var age: Int?
// 等價
var age: Int? = nil;
案例:數組越界
var array = [1, 15, 20, 30]
func get(_ index: Int) -> Int? {
if index < 0 || array.count <= index {
return nil
}
return array[index]
}
print(get(4)) // 輸出:nil
print(get(2)) // 輸出:Optional(20)
注意:上面代碼最后一行輸入Optional(20)
, 為什么會被加上Optional
,這樣還能作為一個Int
進行運算么?當然不可以,因為被加上Optional
后就是可選類型了,如果要使用里面的值,需要進行強制解包。
二、強制解包
可選項是對其他類型的一層包裝,可以將它理解為一個盒子:
- 如果為
nil
,那么它就是個空盒子; - 如果不為
nil
,那么盒子里裝的就是被包裝類型的數據; - 如果要從可選項中取出被包裝的數據(將盒子里裝的東西取出來),需要使用感嘆號(
!
)進行強制解包; - 在取出的可選類型的變量后面加上
!
即可。
var array = [1, 15, 20, 30]
func get(_ index: Int) -> Int? {
if index < 0 || array.count <= index {
return nil
}
return array[index]
}
let num1 = get(1)!
let num2 = get(2)!
let result = num1 + num2
print(result) // 輸出:35
// 等價
let num1 = get(1)
let num2 = get(2)
let result = num1! + num2!
如果對值為nil
的可選項(空盒子)進行強制解包,將會產生運行時錯誤
var age: Int?
let num = age!
print(num)
運行結果:
解決辦法:
- 判斷可選項是否為
nil
; - 使用可選項綁定來判斷可選項是否包含值。
三、可選項綁定
如果包含值就自動解包,把值賦給一個臨時的常量(let
)或變量(var
),並返回true
,否則返回false
。
// 判斷是否為nil
let number = Int("123kkk")
if number != nil {
print("轉換成功:\(number!)")
} else {
print("轉換失敗")
}
/*
輸出:轉換失敗
*/
// 使用可選項綁定
if let number = Int("123") {
print("轉換成功:\(number)")
} else {
print("轉換失敗")
}
/*
輸出:轉換成功:123
*/
注意:number
的作用域僅限后面緊跟的大括號。
當一個變量是可選項時,Xcode會提示:
示例一:
if let first = Int("12") {
if let second = Int("34") {
if first < second && second < 100 {
print("\(first) < \(second) < 100")
}
}
}
/*
輸出:12 < 34 < 100
*/
示例一的等價寫法:
if let first = Int("12"),
let second = Int("34"),
first < second && second < 100 {
print("\(first) < \(second) < 100")
}
/*
輸出:12 < 34 < 100
*/
注意:可選項綁定在if條件中,只能使用逗號進行隔開。
while循環中使用可選項綁定
場景:遍歷數組,將遇到的整數都加起來,如果遇到負數或者非數字,停止遍歷。
示例:
var strs = ["10", "20", "-20", "ab", "30"]
var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
sum += num
index += 1
}
print(sum);
輸出:30
四、空合並運算符??
Swift對空合並運算符的定義:
public func ?? <t>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
public func ?? <t>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
格式: a ?? b
- a是可選項;
- b是可選項或者不是可選項;
- b和a的存儲類型必須相同;
- 如果a不為nil,就返回a;
如果a為nil,就返回b;
如果b不是可選項,返回a時會自動解包。
示例:
let a: Int? = 1
let b: Int? = 2
let c = a ?? b
// c是Int?, Optional(1)
let a: Int? = nil
let b: Int? = 2
let c = a ?? b
// c是Int?, Optional(2)
let a: Int? = nil
let b: Int? = nil
let c = a ?? b
// c是Int?, nil
let a: Int? = 1
let b: Int = 2
let c = a ?? b
// c是Int, 1
let a: Int? = nil
let b: Int = 2
let c = a ?? b
// c是Int, 2
// 等價寫法
let a: Int? = nil
let b: Int = 2
let c: Int
if let tmp = a {
c = tem
} else {
c = b
}
通過上面示例可以看到,空合並運算符返回什么類型,取決於運算符后面的類型。
4.1. 多個??一起使用
let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3
// c是Int, 1
let a: Int? = nil
let b: Int? = 2
let c = a ?? b ?? 3
// c是Int, 2
let a: Int? = nil
let b: Int? = nil
let c = a ?? b ?? 3
// c是Int, 3
4.2. ??根if let配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
print(c);
}
// 類似於if a != nil || b != nil
let a: Int? = nil
let b: Int? = 2
if let c = a,
let d = b {
print(c);
print(d);
}
// 類似於if a != nil && b != nil
五、guard的使用
格式:
guard 條件 else {
// ToDo
退出當前作用域
// return、break、continue、throw error
}
特點:
- 當條件為
false
時,執行大括號里面的代碼;當條件為true
時,就會跳過guard
語句; guard
語句必須有退出指令;guard
語句適合用來”提前退出“;- 當使用
guard
語句進行可選項綁定時,綁定的常量(let)、變量(var)也能在外層作用域中使用。
簡單登錄案例:
func login(_ info: [String : String]) {
let username: String
if let tmp = info["username"] {
username = tmp
} else {
print("請輸入用戶名")
return
}
let password: String
if let tmp = info["password"] {
password = tmp
} else {
print("請輸入密碼")
return
}
print("用戶名:\(username), 密碼:\(password), 登陸ing")
}
login(["username": "idbeny", "password": "123456"])
login(["password": "123456"])
login(["username": "idbeny"])
/*
輸出:
用戶名:idbeny, 密碼:123456, 登陸ing
請輸入用戶名
請輸入密碼
*/
使用guard:
func login(_ info: [String : String]) {
guard let username = info["username"] else {
print("請輸入用戶名")
return
}
guard let password = info["password"] else {
print("請輸入密碼")
return
}
print("用戶名:\(username), 密碼:\(password), 登陸ing")
}
login(["username": "idbeny", "password": "123456"])
login(["password": "123456"])
login(["username": "idbeny"])
/*
輸出:
用戶名:idbeny, 密碼:123456, 登陸ing
請輸入用戶名
請輸入密碼
*/
分析:通過上面的if
和guard
案例可以看出,某些場景下guard
更簡潔。
擴展:字典取值如果key存在返回可選類型的value,不存在就返回nil;數組取值如果下標存在返回對應的值(不是可選類型),否則直接報錯(越界)。
六、隱式解包
在某些情況下,可選項一旦被設定值之后,就會一直擁有值。在這種情況下,可以去掉檢查,也不必每次訪問的時候都進行解包,因為他能確定每次訪問的時候都有值。
可以在類型后面加個感嘆號!
定義一個隱式解包的可選項。
let num1: Int! = 10
let num2: Int = num1
if num1 != nil {
print(num1)
}
if let num3 = num1 {
print(num3)
}
/*
輸出:
10
10
*/
在類型后面加上!
也代表是可選類型,同?
一樣,只是加上感嘆號后會自動解包,不需要強制解包。
如果num1
有值,就會返回10
,而不是Optional(10)
;如果num1
為空,就會報錯,因為對空的可選類型進行強制解包是會報錯的。
所以,如果能夠隱式解包的應用場景就是能夠確保可選項一定是有值的,否則就會容易出錯。同時建議少用隱式解包(既然不能非空,直接賦值就可以了,不需要包裝成可選類型)。
七、字符串插值
可選項在字符串插值或者直接打印時,編譯器會發出警告。
至少有3種方法消除警告(編譯器有給出相關提示):
- 強制解包
print("age:\(age!)")
// 輸出:age:10
- 字符串描述(不會解包)
print("age:\(String(describing: age))")
// 輸出:age:Optional(10)
- 空合並運算符
print("age:\(age ?? 0)")
// 輸出:age:10
八、多重可選項
格式:類型后面多個?
案例一:
var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10
print(num2 == num3) // true
/*
num1結構:
—— Int?
—— Int 10
num2結構:
—— Int??
—— Int?
—— Int 10
num3結構:
—— Int??
—— Int?
—— Int 10
num2和num3是等效的
*/
案例二:
var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil
print(num2 == num3) // false
/*
num1結構:
—— Int?
num2結構:
—— Int??
—— Int?
num3結構:
—— Int??
*/
可以使用lldb
指令查看上面案例的區別:frame variable -R
或 fr v -R
。
查看案例一:
查看案例二:
如果是none
,就代表是一個空盒子,后面的內容就不需要關心了。
如果是some
,代表裝有值的盒子。