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 运用频率最高的特性之一,也是安全性的柱石。除了常规的 ifguard 外还有不少高档特性,如: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 是个「独立类型」,IntInt? 有着本质的区别,不是一回事。

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 :代表「空」,即 nil

    let 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
  }
}

flatMapmap 仅有的区别就是其 transform 闭包回来的泛型类型的可选类型

总归,mapflatMap 是 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 ,还能够用于 iffor等,如:

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 letshorthand


本文一切关于 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,因此 IntInt? 有着本质的区别。

Enum Optional 上界说了许多便捷的操作办法,如:mapflatMap 等,充分利用它们能够写出更高雅的代码。

Swift 5.7 简化了 Optional binding,if let foo = foo {} –> if let foo {}

参考资料

swift/Optional.swift

swift-evolution/0345-if-let-shorthand