Xcode 8.3 和 Swift 3.1 现在已经发布了(3/28)!
可以通过 AppStore 或 Apple Developer 进行下载
Xcode 8.3 优化了 Objective-C 与 Swift 混编项目的编译速度.
Swift 3.1 版本包含一些期待已久的 Swift package manager 功能和语法本身的改进。
如果您没有密切关注 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 提案号码。我已经列出了每个提案的链接,以便您可以发现每个特定更改的完整详细信息。我建议您尝试在Playground上验证新的功能,以便更好地了解所有更改的内容。
Note:如果你想了解 swift 3.0 中的新功能,可以看这篇文章。
语法改进
首先,我们来看看这个版本中的语法改进,包括关于数值类型的可失败构造器
(Failable Initializers
),新的序列函数等等。
可失败的数值转换构造器(Failable Numeric Conversion Initializers)
Swift 3.1 为所有数值类型 (Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double)
添加了可失败构造器。
这个功能非常有用,例如,以安全、可恢复的方式处理外源松散类型数据的转换,下面来看 Student 的 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
,像这样
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]。
构造一个斐波纳契无限序列:
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 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 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:这种情况,可以使用尾随闭包的写法:
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}
Concrete Constrained Extensions(姑且翻译为类的约束扩展吧)
Swift 3.1允许您扩展具有类型约束的通用类型。以前,你不能像这样扩展类型,因为约束必须是一个协议。我们来看一个例子。
例如,Ruby on Rails提供了一种isBlank
检查用户输入的非常有用的方法。以下是在Swift 3.0中用 String
类型的扩展实现这个计算型属性:
// 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 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 3.1
extension Optional where Wrapped == String {
var isBlank: Bool {
return self?.isBlank ?? true
}
}
这就用更少的代码实现了和原先相同的功能~
泛型嵌套(Nested Generics)
Swift 3.1允许您将嵌套类型与泛型混合。作为一个练习,考虑这个(不是太疯狂)的例子。每当某个团队领导raywenderlich.com想在博客上发布一篇文章时,他会分配一批专门的开发人员来处理这个问题,以满足网站的高质量标准:
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
外部类中,并使两个类都通用。这是团队如何寻找我在网站上发布的教程和文章:
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()))
但实际上,在这种情况下,您可以简化该代码。如果嵌套的内部类型使用通用外部类型,那么它默认继承父类的类型。因此,您不需要如此声明:
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泛型入门的教程。
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编译器时,例如您要使用Swift 3.0行为,则需要使用针对该特定兼容性版本编译的标准库版本。如果您使用版本3.1模式编译的,那么您根本就没有正确的代码
因此,@available除了现有平台版本 [SE-0141] 之外,Swift 3.1扩展了该属性以支持指定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中可用性属性的教程。
逃逸闭包(Escaping Closures)
在Swift 3.0 [ SE-0103 ] 中函数中的闭包的参数是默认是不逃逸的(non-escaping)。在Swift 3.1中,您可以使用新的函数withoutActuallyEscaping()
将非逃逸闭包转换为临时逃逸。
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
}
}
此函数同时加载两个闭包,然后在两个完成之后返回。
f
与g
进入函数后由非逃逸状态,分别转换为逃逸闭包:escapableF
和escapableG
。- async(execute:) 的调用需要逃逸闭包,我们在上面已经进行了转换。
- 通过运行
sync(flags: .barrier)
,您确保async(execute:)
方法完全完成,稍后将不会调用闭包。 - 在范围内使用
escapableF
andescapableG
.
如果你存储临时逃离闭包(即真正逃脱)这将是一个Bug。未来版本的标准库可以检测这个陷阱,如果你试图调用它们。
Swift Package Manager 更新
啊,期待已久的 Swift Package Manage 的更新了!
可编辑软件包(Editable Packages)
Swift 3.1将可编辑软件包(editable packages)
的概念添加到Swift软件包管理器 [ SE-0082 ]。
该swift package edit
命令使用现有的Packages
并将其转换为editable Packages
。使用--end-edit
命令将 package manager
还原回 规范解析的软件包(canonical resolved packag)。
版本固定(Version Pinning)
Swift 3.1 添加了版本固定的概念[ SE-0145 ]。该 pin
命令 固定一个或所有依赖关系如下所示:
$ swift package pin --all // 固定所有的依赖
$ swift package pin Foo // 固定 Foo 在当前的闭包
$ swift package pin Foo --version 1.2.3 // 固定 Foo 在 1.2.3 版本
使用unpin
命令恢复到以前的包版本:
$ 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 禁用了在C语言 模块映射(module maps)中的自动链接的功能:
// 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 或者查看官方的的Swift CHANGELOG,您可以在其中阅读所有更改的信息。或者您可以使用它来了解 Swift 4.0 中的内容!