---
layout:     post
title:      Swift 3.1 的新变化「译」
subtitle:   What’s New in Swift 3.1?
date:       2017-03-30
author:     BY
header-img: img/post-bg-iWatch.jpg
catalog: true
tags:
    - iOS
    - Swift
    - Xcode
---


![](https://koenig-media.raywenderlich.com/uploads/2017/03/WhatsNewSwift3.1-feature-1-250x250.png)

Xcode 8.3 和 Swift 3.1 现在已经发布了(3/28)！

可以通过 [AppStore](https://itunes.apple.com/us/app/xcode/id497799835?ls=1&mt=12#) 或 [Apple Developer](https://developer.apple.com/download/more/) 进行下载

![](https://ww2.sinaimg.cn/large/006tNbRwgy1fe2it3xwt6j30s50g3mya.jpg)

Xcode 8.3 优化了 Objective-C 与 Swift 混编项目的编译速度.

Swift 3.1 版本包含一些期待已久的 [Swift package manager](https://github.com/apple/swift-package-manager) 功能和语法本身的改进。

如果您没有密切关注 [Swift Evolution](https://github.com/apple/swift-evolution) 进程，请继续阅读 - 本文非常适合您！

在本文中，我将强调Swift 3.1中最重要的变化，这将对您的代码产生重大影响。我们来吧！😃

## 开始

Swift 3.1与Swift 3.0源代码兼容，因此如果您已经使用Xcode 中的 `Edit \ Convert \ To Current Swift Syntax ...` 将项目迁移到Swift 3.0，新功能将不会破坏您的代码。不过，苹果已经在Xcode 8.3中支持Swift 2.3。所以如果你还没有从Swift 2.3迁移，现在是时候这样做了！

在下面的部分，您会看到链接的标签，如`[SE-0001]`。这些是 [Swift Evolution](https://apple.github.io/swift-evolution/) 提案号码。我已经列出了每个提案的链接，以便您可以发现每个特定更改的完整详细信息。我建议您尝试在Playground上验证新的功能，以便更好地了解所有更改的内容。

> Note:如果你想了解 swift 3.0 中的新功能，可以看[这篇文章](https://www.raywenderlich.com/135655/whats-new-swift-3)。

## 语法改进

首先，我们来看看这个版本中的语法改进，包括关于数值类型的`可失败构造器`（`Failable Initializers`），新的序列函数等等。

#### 可失败的数值转换构造器（Failable Numeric Conversion Initializers）

Swift 3.1 为所有数值类型 `(Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double)` 添加了[可失败构造器](https://www.swiftmi.com/topic/121.html)。

这个功能非常有用，例如，以安全、可恢复的方式处理外源松散类型数据的转换，下面来看 Student 的 JSON 数组的处理：

```swift
class Student {
  let name: String
  let grade: Int
 
  init?(json: [String: Any]) {
    guard let name = json["name"] as? String,
          let gradeString = json["grade"] as? String,
          let gradeDouble = Double(gradeString),
          let grade = Int(exactly: gradeDouble)  // <-- 3.1 的改动在这
    else {
        return nil
    }
    self.name = name
    self.grade = grade
  }
}
 
func makeStudents(with data: Data) -> [Student] {
  guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
        let jsonArray = json as? [[String: Any]] else {
    return []
  }
  return jsonArray.flatMap(Student.init)
}
 
let rawStudents = "[{\"name\":\"Ray\", \"grade\":\"5.0\"}, {\"name\":\"Matt\", \"grade\":\"6\"},
                    {\"name\":\"Chris\", \"grade\":\"6.33\"}, {\"name\":\"Cosmin\", \"grade\":\"7\"}, 
                    {\"name\":\"Steven\", \"grade\":\"7.5\"}]"
let data = rawStudents.data(using: .utf8)!
let students = makeStudents(with: data)
dump(students) // [(name: "Ray", grade: 5), (name: "Matt", grade: 6), (name: "Cosmin", grade: 7)]
```
在 `Student` 类中使用了一个可失败构造器将 `grade` 属性从 `Double ` 转变为 `Int`，像这样

```swift
let grade = Int(exactly: gradeDouble)
```

如果`gradeDouble`不是整数，例如6.33，它将失败。如果它可以用一个正确的表示Int，例如6.0，它将成功。

> Note：虽然`throwing initializers` 可以用来替代 `failable initializers`。但是使用 `failable initializers` 会更好，更符合人的思维。

##### 新的序列函数（Sequence Functions）

swift3.1添加了两个新的标准库函数在 `Sequence` 协议中：`prefix(while:)``和prefix(while:)`[[SE-0045]](https://github.com/apple/swift-evolution/blob/master/proposals/0045-scan-takewhile-dropwhile.md)。

构造一个[斐波纳契](https://en.wikipedia.org/wiki/Fibonacci_number)无限序列：

```swift
let fibonacci = sequence(state: (0, 1)) {
  (state: inout (Int, Int)) -> Int? in
  defer {state = (state.1, state.0 + state.1)}
  return state.0
}
```

在Swift 3.0中，您只需指定`迭代次数`即可遍历fibonacci序列：

```swift
// Swift 3.0
for number in fibonacci.prefix(10) {
  print(number)  // 0 1 1 2 3 5 8 13 21 34
}
```

在swift 3.1中，您可以使用`prefix(while:)`和`drop(while:)`获得符合条件在两个给定值之间的序列中的所有元素，就像这样：

```swift
// Swift 3.1
let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100})
for element in interval {
  print(element) // 144 233 377 610 987
}
```

`prefix(while:)`返回满足某个谓词的最长子序列。它从序列的开头开始，并停在给定闭包返回false的第一个元素上。

`drop(while:)` 相反：它返回从给定关闭返回false的第一个元素开始的子序列，并在序列结尾完成。

> Note:这种情况，可以使用尾随闭包的写法：
> 
> ```swift
> let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}
> ```


## Concrete Constrained Extensions（姑且翻译为类的约束扩展吧）

Swift 3.1允许您扩展具有类型约束的通用类型。以前，你不能像这样扩展类型，因为约束必须是一个协议。我们来看一个例子。

例如，Ruby on Rails提供了一种`isBlank`检查用户输入的非常有用的方法。以下是在Swift 3.0中用 `String` 类型的扩展实现这个**计算型属性**：

```swift
// Swift 3.0
extension String {
  var isBlank: Bool {
    return trimmingCharacters(in: .whitespaces).isEmpty
  }
}
 
let abc = " "
let def = "x"
 
abc.isBlank // true
def.isBlank // false
```

如果你希望`isBlank`计算型属性为一个可选值所用，在swift 3.0中，你将要这样做

```swift
// Swift 3.0
protocol StringProvider {
  var string: String {get}
}
 
extension String: StringProvider {
  var string: String {
    return self
  }
}
 
extension Optional where Wrapped: StringProvider {
  var isBlank: Bool {
    return self?.string.isBlank ?? true
  }
}
 
let foo: String? = nil
let bar: String? = "  "
let baz: String? = "x"
 
foo.isBlank // true
bar.isBlank // true
baz.isBlank // false
```

这创建了一个采用 `String` 的 `StringProvider` 协议而在你使用StringProvider扩展可选的 wrapped 类型时,添加isBlank方法。

Swift 3.1中，用来替代协议方法，扩展具体类型的方法像这样：

```swift
// Swift 3.1
extension Optional where Wrapped == String {
  var isBlank: Bool {
    return self?.isBlank ?? true
  }
}
```
这就用更少的代码实现了和原先相同的功能~

## 泛型嵌套（Nested Generics）

Swift 3.1允许您将嵌套类型与泛型混合。作为一个练习，考虑这个（不是太疯狂）的例子。每当某个团队领导raywenderlich.com想在博客上发布一篇文章时，他会分配一批专门的开发人员来处理这个问题，以满足网站的高质量标准：

```swift
class Team<T> {
  enum TeamType {
    case swift
    case iOS
    case macOS
  }
 
  class BlogPost<T> {
    enum BlogPostType {
      case tutorial
      case article
    }
 
    let title: T
    let type: BlogPostType
    let category: TeamType
    let publishDate: Date
 
    init(title: T, type: BlogPostType, category: TeamType, publishDate: Date) {
      self.title = title
      self.type = type
      self.category = category
      self.publishDate = publishDate
    }
  }
 
  let type: TeamType
  let author: T
  let teamLead: T
  let blogPost: BlogPost<T>
 
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost<T>) {
    self.type = type
    self.author = author
    self.teamLead = teamLead
    self.blogPost = blogPost
  }
}
```

将`BlogPost`内部类嵌套在其对应的`Team`外部类中，并使两个类都通用。这是团队如何寻找我在网站上发布的教程和文章：

```swift
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "Pattern Matching", type: .tutorial, 
     category: .swift, publishDate: Date()))
 
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "What's New in Swift 3.1?", type: .article, 
     category: .swift, publishDate: Date()))
```

但实际上，在这种情况下，您可以简化该代码。如果嵌套的内部类型使用通用外部类型，那么它默认继承父类的类型。因此，您不需要如此声明：

```swift
class Team<T> {
  // original code 
 
  class BlogPost {
    // original code
  }  
 
  // original code 
  let blogPost: BlogPost
 
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    // original code   
  }
}
```

> Note：如果您想了解更多关于Swift中的**泛型**，请阅读我们最近更新的[Swift泛型入门的教程](https://www.raywenderlich.com/154371/swift-generics-tutorial-getting-started)。

##  Swift版本的可用性

您可以使用**#if swift(>= N)** `静态构造`来检查特定的Swift版本：

```swift
// Swift 3.0
#if swift(>=3.1)
  func intVersion(number: Double) -> Int? {
    return Int(exactly: number)
  }
#elseif swift(>=3.0)
  func intVersion(number: Double) -> Int {
    return Int(number)
  }
#endif
```

然而，当使用Swift标准库时，这种方法有一个主要缺点。它需要为每个受支持的旧语言版本编译标准库。这是因为当您以向后兼容模式运行Swift编译器时，例如您要使用Swift 3.0行为，则需要使用针对该特定兼容性版本编译的标准库版本。如果您使用版本3.1模式编译的，那么您根本就没有正确的代码

因此，@available除了现有平台版本 [[SE-0141]](https://github.com/apple/swift-evolution/blob/master/proposals/0141-available-by-swift-version.md) 之外，Swift 3.1扩展了该属性以支持指定Swift版本号：

```swift
// Swift 3.1
 
@available(swift 3.1)
func intVersion(number: Double) -> Int? {
  return Int(exactly: number)
}
 
@available(swift, introduced: 3.0, obsoleted: 3.1)
func intVersion(number: Double) -> Int {
  return Int(number)
}
```

这个新功能提供了与`intVersionSwift`版本有关的方法相同的行为。但是，它只允许像标准库这样的库被编译一次。编译器然后简单地选择可用于所选择的给定兼容性版本的功能。

> Note：注意：如果您想了解更多关于Swift 的`可用性属性（ availability attributes）`，请参阅我们关于[Swift中可用性属性的教程](https://www.raywenderlich.com/139077/availability-attributes-swift)。

## 逃逸闭包（Escaping Closures）

在Swift 3.0 [[ SE-0103 ]](https://github.com/apple/swift-evolution/blob/master/proposals/0103-make-noescape-default.md) 中函数中的闭包的参数是默认是不逃逸的（non-escaping）。在Swift 3.1中，您可以使用新的函数`withoutActuallyEscaping()`将非逃逸闭包转换为临时逃逸。

```swift
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void,
             on queue: DispatchQueue) {
  withoutActuallyEscaping(f) { escapableF in     // 1
    withoutActuallyEscaping(g) { escapableG in
      queue.async(execute: escapableF)           // 2
      queue.async(execute: escapableG)     
 
      queue.sync(flags: .barrier) {}             // 3
    }                                            // 4
  }
}
```

此函数同时加载两个闭包，然后在两个完成之后返回。

1. `f` 与 `g` 进入函数后由非逃逸状态，分别转换为逃逸闭包:`escapableF`和`escapableG`。
2. async(execute:) 的调用需要逃逸闭包，我们在上面已经进行了转换。
3. 通过运行`sync(flags: .barrier)`，您确保`async(execute:)`方法完全完成，稍后将不会调用闭包。
4. 在范围内使用 `escapableF` and `escapableG`.

如果你存储临时逃离闭包(即真正逃脱)这将是一个Bug。未来版本的标准库可以检测这个陷阱,如果你试图调用它们。

## Swift Package Manager 更新

啊，期待已久的 **Swift Package Manage** 的更新了！

#### 可编辑软件包（Editable Packages）

Swift 3.1将`可编辑软件包(editable packages)`的概念添加到Swift软件包管理器 [[ SE-0082 ]](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#editable-packages)。

该`swift package edit`命令使用现有的`Packages`并将其转换为`editable Packages`。使用`--end-edit`命令将 `package manager` 还原回 **规范解析的软件包（canonical resolved packag）**。

#### 版本固定（Version Pinning）

Swift 3.1 添加了版本固定的概念[ SE-0145 ]。该 `pin` 命令 固定一个或所有依赖关系如下所示：

```swift
$ swift package pin --all      // 固定所有的依赖
$ swift package pin Foo        // 固定 Foo 在当前的闭包
$ swift package pin Foo --version 1.2.3  // 固定 Foo 在 1.2.3 版本

```

使用`unpin`命令恢复到以前的包版本：

```swift
$ swift package unpin —all
$ swift package unpin Foo
```

Package manager 将每个依赖库的版本固定信息存储在 `Package.pins` 文件中。如果该文件不存在，则Package manager 会自动创建。

#### 其他

`swift package reset` 命令将会把 Package 重置干净。

`swift test --parallel` 命令 执行测试。

## 其他改动

在 swift 3.1 中还有一些小改动

#### 多重返回函数
C函数返回两次，例如`vfork` 和 `vfork `已经不用了。他们以有趣的方式改变了程序的控制流程。所以 Swift 社区 已经禁止了该行为，以免导致编译错误。

#### 自动链接失效（Disable Auto-Linking）

[Swift Package Manager](https://github.com/apple/swift-package-manager) 禁用了在C语言 [**模块映射（module maps）**](http://nsomar.com/modular-framework-creating-and-using-them/)中的**自动链接**的功能：

```swift
// Swift 3.0
module MyCLib {
    header “foo.h"
    link “MyCLib"
    export *
}
 
// Swift 3.1
module MyCLib {
    header “foo.h”
    export *
}
```

## 结语

Swift 3.1改善了Swift 3.0的一些功能，为即将到来的Swift 4.0的大改动做准备。这些包括对泛型，正则表达式，更科学的`String`等方面的作出极大的改进。

如果你想了解更多，请转到 [Swift standard library diffs](https://developer.apple.com/reference/swift?changes=latest_minor) 或者查看官方的的[Swift CHANGELOG](https://github.com/apple/swift/blob/master/CHANGELOG.md)，您可以在其中阅读所有更改的信息。或者您可以使用它来了解 **Swift 4.0** 中的内容！

## 著作权声明

本文译自 [What’s New in Swift 3.1?](https://www.raywenderlich.com/156352/whats-new-in-swift-3-1)

由[@柏荧(BY)](http://github.com/qiubaiying)进行翻译,首次发布于 [BY Blog](http://qiubaiying.github.io)，转载请保留原文链接.