Optionals是Swift的核心,并且在第一個(gè)版本中就已經(jīng)存在;optional修飾的值允許我們?cè)陉P(guān)注可能為nil值的時(shí)候書寫整潔的代碼。
如果你剛開始接觸Swift,那么你可能需要熟悉在屬性中添加?的語法;只要你熟悉了這個(gè)語法你就可以從中受益,比如extensions。
在Swift中什么是可選值?
在我們深入了解optionals之前需要了解基本知識(shí)。
屬性、方法、下標(biāo)能夠返回一個(gè)可選值,這個(gè)值可能不為nil可能為nil。多個(gè)引用可以鏈接在一起,這稱為可選鏈接。這是強(qiáng)制解包的另一種方式,稍后將對(duì)其進(jìn)行更詳細(xì)的講解。
下面的示例代碼展示了自定義可選的String并且print字符數(shù)的可選鏈接。
let name: String? = "Antoine van der Lee"
print(name?.count ?? 0)
1. 在Swift中強(qiáng)制解包可選值
強(qiáng)制解包可選值可能返回一個(gè)存在的值或者是一個(gè)導(dǎo)致runtime運(yùn)行時(shí)錯(cuò)誤的空值。
但是在我們深入了解強(qiáng)制解包之前,讓我們先來看看在沒有強(qiáng)制的情況下解包可選值有哪些可能性。
如何解包一個(gè)可選值
在Swift中有很多方法可以解包可選值。你可以使用guard聲明:
let name: String? = "Antoine van der Lee"
guard let unwrappedName = name else {
return
}
print(unwrappedName.count)
或者可以使用if let聲明:
let name: String? = "Antoine van der Lee"
if let unwrappedName = name {
print(unwrappedName.count)
}
或者你可以使用??運(yùn)算符,也稱為nil合并運(yùn)算符。如果它存在,這種將會(huì)返回一個(gè)可選值或者比如下面自定義的默認(rèn)值0:
let name: String? = "Antoine van der Lee"
print(name?.count ?? 0)
使用!強(qiáng)制解包可選值
可以在可選值后面使用!進(jìn)行強(qiáng)制解包。
var name: String? = "Antoine van der Lee"
print(name!.count)
當(dāng)上述示例中的name變量設(shè)置為nil的時(shí)候它將導(dǎo)致致命的運(yùn)行時(shí)錯(cuò)誤,比如下面這種錯(cuò)誤:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
鏈接解包
可以用下面的方式進(jìn)行可選鏈:
struct BlogPost {
let title: String?
}
let post: BlogPost? = BlogPost(title: "Learning everything about optionals")
print(post?.title?.count ?? 0)
同時(shí)對(duì)counts使用強(qiáng)制解包:
let post: BlogPost? = BlogPost(title: "Learning everything about optionals")
print(post!.title!.count)
但是要注意如果你解包最后一個(gè)可選值,那么你仍然將得到一個(gè)可選值;在下面的示例中,我們僅僅解包title而不是post;這就意味著如果post為nil的時(shí)候,那么我們就不能獲取到一個(gè)title值:
let post: BlogPost? = BlogPost(title: "Learning everything about optionals")
print(post?.title!.count) // Prints: Optional(35)
強(qiáng)制解包捕獲erros是可選值最好的方式
使用可選值最好的方式在默認(rèn)的情況下最好不要使用??;一些人甚至建議啟用 force unwrapping SwiftLint rule;這將防止你引用更多不期望的閃退。
但是有時(shí)候如果值為nil的時(shí)候,在編程錯(cuò)誤時(shí)使用強(qiáng)制解包也很好;因此在早期的時(shí)候你可以通過調(diào)試強(qiáng)制解包幫你自己發(fā)現(xiàn)一個(gè)bug。
2.可選是枚舉中的2種情況
很高興知道可選總體來說是枚舉中的2種情況:
enum Optional<Wrapped> {
/// The absence of a value.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
}
但是如果不使用.none,你將使用nil來表示缺失值。
考慮到這一點(diǎn),你還可以使用枚舉將上述name變量定義為可選的:
let name = Optional.some("Antoine van der Lee")
print(name!.count)
或者你可以像切換普通枚舉一樣切換可選值:
func printName(_ name: String?) {
switch name {
case .some(let unwrappedValue):
print("Name is \(unwrappedValue)")
case .none:
print("Name is nil")
}
}
printName(nil) // Prints: "Name is nil"
printName("Antoine van der Lee") // Prints: "Name is Antoine van der Lee"
看看它的文檔你會(huì)發(fā)現(xiàn)一個(gè)可選值附帶了一些非常方便的方法;一個(gè)不錯(cuò)的示例就是map方法:
let sideLength: Int? = Int("20")
let possibleSquare = sideLength.map { $0 * $0 }
print(possibleSquare) // Prints: "Optional(400)"
或者flatMap方法,在本例中該方法僅在得到至少5個(gè)字符的驗(yàn)證時(shí)才返回name:
var name: String? = "Antoine van der Lee"
let validName = name.flatMap { name -> String? in
guard name.count > 5 else { return nil }
return name
}
print(validName) // Prints: "Optional("Antoine van der Lee")"
如果你想了解更多map、flatMap以及compactMap,查看我的博客:<u>CompactMap vs flatMap: The differences explained</u>
擴(kuò)展中的可選
你現(xiàn)在知道一個(gè)可選被定義為一個(gè)枚舉,你能猜到你可以為它寫擴(kuò)展
最常見的示例是擴(kuò)展可選字符串并處理空值:
extension Optional where Wrapped == String {
var orEmpty: String {
return self ?? ""
}
}
var name: String? = "Antoine van der Lee"
print(name.orEmpty) // Prints: "Antoine van der Lee"
name = nil
print(name.orEmpty) // Prints: ""
3.為可選值編寫單元測(cè)試
當(dāng)你在寫測(cè)試的時(shí)候,有一個(gè)很好的方法可以在不強(qiáng)制解包的情況下使用可選值;如果要使用強(qiáng)制解包,你可能會(huì)導(dǎo)致致命錯(cuò)誤,從而阻斷所有測(cè)試的成功。
如果可選值不包含值你可以使用XCTUnwrap,它將會(huì)拋出異常:
func testBlogPostTitle() throws {
let blogPost: BlogPost? = fetchSampleBlogPost()
let unwrappedTitle = try XCTUnwrap(blogPost?.title, "Title should be set")
XCTAssertEqual(unwrappedTitle, "Learning everything about optionals")
}
4.可選的協(xié)議方法
如果你有過編寫Objective-C的經(jīng)驗(yàn),你可能會(huì)錯(cuò)過可選的協(xié)議方法。盡管用Swift編寫可選的協(xié)議方法有更好的方式,但是標(biāo)準(zhǔn)庫(kù)中最常見的方式如下:
@objc protocol UITableViewDataSource : NSObjectProtocol {
@objc optional func numberOfSections(in tableView: UITableView) -> Int
// ...
}
下面允許你使用?調(diào)用方法:
let tableView = UITableView()
let numberOfSections = tableView.dataSource?.numberOfSections?(in: tableView)
5.嵌套可選是一個(gè)重要的功能
雖然SE-0230 – Flatten nested optionals resulting from ‘try?’是刪除嵌套可選的最常見原因之一,但它仍然是一個(gè)重要的功能!
var name: String?? = "Antoine van der Lee"
print(name!!.count)
你解包了一個(gè)可選值仍然會(huì)返回一個(gè)可選值,以前在早期的Swift版本中使用try?運(yùn)算符就是這種情況。
一個(gè)常見的示例是當(dāng)你使用包含可選值字典的時(shí)候:
let nameAndAges: [String:Int?] = ["Antoine van der Lee": 28]
let antoinesAge = nameAndAges["Antoine van der Lee"]
print(antoinesAge) // Prints: "Optional(Optional(28))"
print(antoinesAge!) // Prints: "Optional(28)"
print(antoinesAge!!) // Prints: "28"
你可以看到它基本上只需要使用一個(gè)額外的!或者是?。
總結(jié)
當(dāng)你在Swift中使用optionals的時(shí)候我們涵蓋了很多你需要知道的知識(shí);解包可選值從最基本的使用感嘆號(hào)!!到擴(kuò)展可選枚舉的高級(jí)實(shí)現(xiàn)。
本文翻譯自原文:https://www.avanderlee.com/swift/optionals-in-swift-explained-5-things-you-should-know/