Swift 作为现代、高效、安全的编程言语,其背面有许多高档特性为之支撑。
『 Swift 最佳实践 』系列对常用的言语特性逐个进行介绍,助力写出更简练、更高雅的 Swift 代码,快速完成从 OC 到 Swift 的改变。
该系列内容首要包括:
- Optional
- Enum
- Closure
- Protocol
- Generic
- Property Wrapper
- Structured Concurrent
- Result builder
- Error Handle
- Advanced Collections (Asyncsequeue/OptionSet/Lazy)
- Expressible by Literal
- Pattern Matching
- Metatypes(.self/.Type/.Protocol)
ps. 本系列不是入门级语法教程,需求有必定的 Swift 根底
本文是系列文章的第一篇,首要介绍 Optional。
Optional 作为 Swift 运用频率最高的特性之一,也是安全性的柱石。除了常规的 if
、guard
外还有不少高档特性,如:map、flatMap、Optional Pattern 等,经过它们能够写出更简练、高雅的代码。
Overview
可选类型 (Optional) 能够说是现代高档言语的标配,如:Kotin,Java,Javascript,Dart 等,Swift 也不类外。
在各种支持 Optional 的言语中,相关语法也非常相似。
界说 Optional 类型,最常用的语法是「 Type? 」,如:
let age: Int? = 20
「 Type? 」是语法糖,其完好语法是:Optional<Type>
如上 age
的完好界说是:
let age: Optional<Int> = Optional.some(20)
能够看到,可选类型 Optional
是个「独立类型」,Int
与 Int?
有着本质的区别,不是一回事。
Optional
@frozen 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)
/// Creates an instance that stores the given value.
public init(_ some: Wrapped)
// ...
}
如上,Optional
是个泛型 Enum,含有 2 个 case:
-
none
:代表「空」,即 nillet age: Int? = nil
等价于:
let age: Optional<Int> = .none // Optional.none
-
some
:代表「非空」,关联具体的值let age: Int? = 20
等价于:
let age: Optional<Int> = .some(20)
或:
let age: Optional<Int> = .init(20)
map
完成如下办法,加载 data:
你会怎么做?
func loadData(url: URL?) -> Data?
首要想到的:
func loadData(url: URL?) -> Data? {
guard let url else {
return nil
}
return try? Data.init(contentsOf: url)
}
还能够写得更高雅、简练些:
func loadData(url: URL?) -> Data? {
try? url.map { try Data.init(contentsOf: $0)}
}
map
?!
没错,Optional 完成了 map
办法:
public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U? {
switch self {
case .some(let y):
return .some(try transform(y))
case .none:
return .none
}
}
经过 map
源码能够看到:
- 若 optional 不为 nil,履行闭包
transform
- 不然回来 nil
flatMap
完成如下办法,将 String 类型的 url 转换成 URL:
func transformURL(_ url: String?) -> URL?
现学现用,愉快地写下:
func transformURL(_ url: String?) -> URL? {
url.map { URL(string: $0) }
}
抱愧,编译错误:
Value of optional type 'URL?' must be unwrapped to a value of type 'URL'
原因在于,在办法 transformURL
中,依据类型推演,闭包 map.transform
的回来值类型为 URL
,而非 URL?
那只能老老实实的:
func transformURL(_ url: String?) -> URL? {
guard let url else {
return nil
}
return URL(string: url)
}
非也!
func transformURL(_ url: String?) -> URL? {
url.flatMap { URL(string: $0) }
}
如上,Optional 还完成了 flatMap
:
public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U? {
switch self {
case .some(let y):
return try transform(y)
case .none:
return .none
}
}
flatMap
与 map
仅有的区别就是其 transform
闭包回来的泛型类型的可选类型
总归,map
、flatMap
是 2 个非常便利的办法,能够写出更简练高雅的代码。
Equatable
extension Optional : Equatable where Wrapped : Equatable {
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
}
因为 Optional
有条件的 (Wrapped : Equatable
) 完成了 Equatable
协议,故能够对两个契合条件的 Optional
类型直接进行判等操作
该运算符还可用于 non-optional 与 optional 间,其间 non-optional value 先主动转换成 optional,再进行判等操作,如:
let num: Int = 1
let numOptional: Int? = 1
if num == numOptional {
// here!
}
Optional Pattern
Pattern-Matching Operator (~=),模式匹配运算符,首要用于 case
匹配
因为 Optional 完成了该运算符,故能够经过 case
句子判断 Optional 是否为 nil
:
func transformURL(_ url: String?) -> URL? {
switch url {
case let url?: // 等价于 case let .some(url):
return URL(string: url)
case nil:
return nil
}
}
上面这种写法与直接用 if
/guard
判断比较并没什么优势,更别说与 flatMap
版别比较了。
但关于一些组合的判断即十分便利,如上面说到的 ==
运算符的完成:
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
假如用 if
判断改写:
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
if let lhs, let rhs {
return lhs == rhs
}
else if lhs == nil, rhs == nil {
return true
}
else {
return false
}
}
很明显,if
版别的可读性比 switch...case
版别差。
case
不仅能够用于 switch
,还能够用于 if
、for
等,如:
let numOptional: Int? = 0
if case .none = numOptional { // 等价于 if numOptional == nil
// ...
}
if case let num? = numOptional { //等价于 if let num = numOptional
// ...
}
如下,打印 scores
中一切及格的分数,你会如何完成?
let scores: [Int?] = [90, nil, 80, 100, 40, 50, 60]
经过 for case let
能够写出很高雅的代码:
// 等价于 for case let .some(score) in scores where score >= 60
for case let score? in scores where score >= 60 {
print(score)
}
用函数式形式也能够写出很高雅的代码
let passingScores = scores.compactMap { score in score.flatMap { $0 >= 60 ? $0 : nil } }
关于 Pattern-Matching 的具体介绍将在后续专题文章中打开
if let
shorthand
本文一切关于 if let
的示例是否感觉有点怪,如:
public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
if let lhs, let rhs {
// ...
}
// ...
}
其间,if let
咱们了解的写法是不是:
if let lhs = lhs, let rhs = rhs {
// ...
}
上述 2 种写法是等价的
第一个版别是 Swift 5.7 完成的新特性,用于简化「 Optional binding 」:swift-evolution/0345-if-let-shorthand
关于一切条件控制句子都适用:
if let foo { ... }
if var foo { ... }
else if let foo { ... }
else if var foo { ... }
guard let foo else { ... }
guard var foo else { ... }
while let foo { ... }
while var foo { ... }
小结
Optional 本质上是 Enum,因此 Int
与 Int?
有着本质的区别。
Enum Optional 上界说了许多便捷的操作办法,如:map
、flatMap
等,充分利用它们能够写出更高雅的代码。
Swift 5.7 简化了 Optional binding,if let foo = foo {}
–> if let foo {}
。
参考资料
swift/Optional.swift
swift-evolution/0345-if-let-shorthand