# Swift 5.6 新特性

By [zzzwco](https://paragraph.com/@zzzwco) · 2022-04-08

---

不可用条件（#unavailable）
-------------------

`#available` 用于根据不同的平台、版本进行条件编译：

    if #available(iOS 15, *) {
      // 通配符 * 表示 Apple 所有平台，如果是 iOS，则要求 >= 15
    } else {
      // iOS 15 以下
    }
    

Swift 5.6 引入了 `#unavailable` ，它和 `#available` 的意思正好相反，下面的示例和上面示例中的 else 分支表达的意思是一样的。

    if #unavailable(iOS 15) {
      // iOS 15 以下
    }
    

它也支持同时指定多个平台：

    if #unavailable(iOS 15, macOS 12) {
      // iOS 15 以下， macOS 12 以下
    }
    

> 注意：使用 `#unavailable` 使不需要通配符，我们的目的是为了使用它具体指出不可用的环境，使用 \* 会造成歧义。

类型占位符（\_、 \_?）
--------------

Swift 5.6 支持使用占位符 `_` 或 `_?` 表示需要声明的类型，我们无需显示地指定类型，编译器会根据上下文自行推断。

    let complexType: [Int: _] = [1: [1], 2: [[2]], 3: [(1, 2)], 4: [{}]]
    // 编译器会将 complexType 的类型推断为 [Int : [Any]]
    
    let complexType: [Int: _] = [1: [1], 2: [[2]], 3: [(1, 2)], 4: nil]
    // 编译器会将 complexType 的类型推断为 [Int : [Any]?]
    

CodingKeyRepresentable 协议
-------------------------

先来看看下面的代码，我们想对字典进行编码，这个字典有点特殊，它的 key 是枚举类型。

    enum AnimalType: String, Codable {
      case cat
      case dog
    }
    
    struct Animal: Codable {
      var name: String
      var age: String
    }
    
    let pets: [AnimalType: Animal] = [
      .cat: .init(name: "Mao", age: "3"),
      .dog: .init(name: "Biu", age: "2")
    ]
    let petsData = try! JSONEncoder().encode(pets)
    print(String(decoding: petsData, as: UTF8.self))
    // ["cat",{"name":"Mao","age":"3"},"dog",{"name":"Biu","age":"2"}]
    

打印的结果与预期不符，因为非 String/Int 类型的 key 值，Swift 在转换时无法正确处理。如果我们将这段编码后的字符串以 JSON 形式向服务端传参时，就会出现错误。为此，我们不得不做额外的工作来进行数据转换。

Swift 5.6 新增的 `CodingKeyRepresentable` 协议很好的解决了这个问题，它支持自定义 key 值的数据类型。我们让 AnimalType 遵循该协议，再次打印的结果与预期一致：

    // {"dog":{"name":"Biu","age":"2"},"cat":{"name":"Mao","age":"3"}}
    

any 关键字
-------

any 和 Any、AnyObject、AnyClass 很像，但它们的关系就像雷锋和雷峰塔。Any 开头的一般表示的是擦除类型信息的**类型**，any 是一个**关键字**，用来修饰一种特殊的类型：存在类型（existential types）。

存在类型是一个比较抽象的概念，如果一定要给它下个定义，我会这样描述它：作为类型的协议。比如下面的代码，

    protocol UIMode {
      var color: Color { get set }
    }
    
    struct LightMode: UIMode {
      var color: Color = .white
    }
    
    struct DarkMode: UIMode {
      var color: Color = .black
    }
    
    struct ModeManager {
      var mode: UIMode
    }
    

在 ModeManager 中 ，协议 UIMode 也被称作存在类型。我们使用一个 `DarkMode()` 实例来初始化 ModeManager 后，还可以使用 `LightMode()` 来替换原有的 mode。在编译期，mode 的类型是 UIMode。在运行时，mode 真正的类型是 LightMode、DarkMode 或其它任意遵循该协议的类型，它的值是可以**动态分发**的。

我们可以把存在类型的值想象成一个盒子，这个盒子可以动态地容纳所有符合该协议类型的值。只要是符合类型的值，相互之间可以动态替换。

我们将上面的代码结合泛型改造一下：

    struct ModeManager<T: UIMode> {
      var mode: T
    }
    
    var manager = ModeManager(mode: DarkMode())
    manager.mode = LightMode()
    

这段代码是无法通过编译的，因为我们在初始化 ModeManager 时传入的是 DarkMode 类型，泛型在编译层面就已经将 mode 约束成了 DarkMode 类型，当我们使用 LightMode 类型的值去改变 mode 时，编译器会报错。

通过对比我们可以看出，当协议作为类型时，它也可以被称为存在类型。如果协议作为泛型的约束，它就无法在运行时动态改变其类型，相应的值只能**静态分发**。另外，不透明类型中使用 `some` 关键字修饰的协议，也无法使用不同类型，它要求我们始终使用遵循该协议的特定类型。

动态虽好，但效率不及静态。存在类型带来了性能损耗，但它是我们常用的写法。因此 Swift 5.6 引入了 any 关键字，目的就是提醒我们存在类型的负面影响。同时，我们应该尽可能地避免使用 any。

    struct ModeManager {
       var mode: any UIMode
    }
    

从 Swift 6 开始，当我们使用存在类型时，编译器会强制要求使用 any 关键字标记，否则会报错。

前文我们提到过，Any 开头的类型一般是擦除类型信息的。它具有一定的动态特性，但会带来一定的性能损耗。这个规律在 SwiftUI 中也是适用的，SwiftUI 中的 AnyView 我们也要慎用。但也不能一概而论，比如 AnyPublisher 我们还是会用到。因此，到底要不要擦除类型信息来换取一定的灵活性，我们要在性能和灵活之间作一个较为平衡的选择。

---

*Originally published on [zzzwco](https://paragraph.com/@zzzwco/swift-5-6)*
