概述
Swift 3.1 和 Swift 3.0 是源码兼容的,所以如果已经使用 Edit\Convert\To Current Swift Syntax… 将项目迁移到了 Swift 3.0 的话,新功能将不会破坏我们的代码。不过,苹果在 Xcode 8.3 中已经抛弃了对 Swift 2.3 的支持。所以如果还没有从 Swift 2.3 迁移过来,现在要抓紧做了!
1. 可失败数值转换初始化方法
Swift 3.1 为所有数字类型 (Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double) 实现了可失败初始化方法,要么完全成功、不损失精度,要么返回 nil 。
例如以下处理JSON的代码
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,就会成功。
##2. 新的序列函数
Swift 3.1 为标准库的 Sequence 协议增加了两个新函数,用于数据过滤:`prefix(while:)` 和` drop(while:)`
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:) `返回满足某 predicate 的最长子序列。从序列的开头开始,并且在第一个从给定闭包中返回 false 的元素处停下。
`drop(while:) `做相反的操作:从第一个在闭包中返回 false 的元素开始,直到序列的结束,返回此子序列。
##3. Concrete Constrained Extensions
Swift 3.1 允许我们扩展具有 concrete type constraint 的泛型。之前不能扩展这样的类型,因为约束必须是一个协议。
例如,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
如果想要 string 可选值 也能用 isBlank 计算属性,在 Swift 3.0 中要这么做:
// 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```
我们创建了一个 StringProvider 协议供 String 采用。当拆包类型是` StringProvider `的时候使用它扩展 Optional,添加 `isBlank `方法。
Swift 3.1 可以用这样的协议来扩展 concrete type:
```swift
// Swift 3.1
extension Optional where Wrapped == String {
var isBlank: Bool {
return self?.isBlank ?? true
}
}```
##4. 泛型嵌套
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> {
// 本来的代码
class BlogPost {
// 本来的代码
}
// 本来的代码
let blogPost: BlogPost
init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
// 本来的代码
}
}```
>**注意:**如果想学习 Swift 中的**泛型**,读一读这篇最近更新的教程 [getting started with Swift generics](https://www.raywenderlich.com/154371/swift-generics-tutorial-getting-started) 。
##5. Swift 版本可用性
我们可以使用 Swift 版本的` #if swift(>= N)`
**静态构造器**,像这样:
```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 3.0 的行为,则需要使用针对该版本编译的标准库。如果使用 3.1 版本的标准库,就根本没有正确的代码。
所以,Swift 3.1 扩展了 `@available`属性
```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)
}```
这个新功能与 `intVersion` 方法相同。但是,它只允许像标准库这样的库被编译一次。编译器随后只要选择与对应版本兼容的功能即可。
>**注意:**如果想学习 Swift 的 **availability attributes**,看看这篇教程 [availability attributes in Swift](https://www.raywenderlich.com/139077/availability-attributes-swift)。
##6. Swift 包管理器的更新
- **Editable Packages**
Swift 3.1 在 [Swift 包管理器](https://github.com/apple/swift-package-manager) 中新增了 **Editable Packages** 概念
`swift package edit`命令可以将现有包转换为可编辑的。可编辑的包将会替换 **dependency graph** 中的规范包。使用 `—end-edit`
命令将包管理器还原回**规范解析的包**。
- **Version Pinning**
Swift 3.1 在特定版本的 [Swift 包管理器](https://github.com/apple/swift-package-manager) 中新增了 **version pinning** 概念。 `pin`命令会像这样固定一个或多个依赖:
$ swift package pin --all // pin 所有依赖
$ swift package pin Foo // 把 Foo pin 在当前解析版本
$ swift package pin Foo --version 1.2.3 // 把 Foo pin 在 1.2.3```
使用 unpin
命令恢复到以前的包版本:
$ swift package unpin —all$ swift package unpin Foo
包管理器在 Package.pins 中存储每个包的活跃版本 pin 信息。如果文件不存在,包管理器则会按照包 manifest 中指定的要求自动创建该文件,这是 automatic pinning 过程的一部分。
-
其它
swift package reset
命令将包重置为干净状态,不会检出当前的依赖关系或 build artifact。还有,使用swift test --parallel
命令并行执行测试。
==
参考文献
英文原版:What’s New in Swift 3.1?
一篇文章帮你彻底了解 Swift 3.1 的新内容 感谢翻译🙏