What’s New in Swift 3.1?
如果你最近没有关注过 Swift 路线图,请接着阅读——这篇文章正是你需要的! 在本文,我将集中介绍 Swift 3.1 的重要改变,这些都会对你的代码产生重大影响。让我们开始吧:] 开始Swift 3.1 和 Swift 3.0 保持源代码兼容,因此新特性不会破坏你的代码,如果你的项目已经用 Xcode 的 EditConvertTo Current Swift Syntax… 迁移到 Swift 3.0 的话。但是,苹果已经在 Xcode 8.3 中终止了对 Swift 2.3 的支持。因此如果你还没有从 Swift 2.3 迁移过来,现在你就需要去迁移了。 在后面的内容中,你会看到一些类似 [SE-0001] 一样的标签。这些是 Swift 路线图的提案编号。我引用了每个提案编号,这样你可以了解每个改变的完整细节。我建议你在 playground 中测试这些特性,这样你就会对每个改变有更深刻的理解。 打开 Xcode,选择 FileNewPlayground…。选择 iOS 平台,命名随意,保存位置随意。在阅读本文的过程中,这个 Playground 会用于测试每个特性。
语法改进首先,来看一眼这个版本的语法改进,包括:数值类型的失败初始化、新的sequence 函数等等。 允许失败的数值转换的初始化方法Swift 3.1 实现了对所有数值类型(Int,Int8,Int16,Int32,Int64,UInt,UInt8,UInt16,UInt32,UInt64,Float,Float80,Double)的允许失败的初始化方法,要么成功不丢失任何信息,要么简单返回 nil。[SE-0080] 这个特性是有用的,例如,可以以一种能够恢复的方式,将来自于外部源的松散类型数据转换成安全类型。例如,你用这样的方式处理一个关于学生的 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 feature here
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,它会转换失败。如果它是一个整数字,比如 6.0,它会转换成功。
新的 Sequence 函数Swift 3.1 新增了两个函数用于数据过滤,它们被添加到标准库的 Sequence 协议中:prefix(while:) 和 drop(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 中,你可以简单地指定迭代次数,来产生斐波那契序列: // 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:) 则相反: 它返回一个子序列,当该序列中的元素不满足闭包中指定的条件时,子序列开始,直到序列的最后一个值。
具化带约束的扩展Swift 3.1 允许你用一个具体的类型约束来扩展泛型。在此之前,你无法扩展这种类型,因为约束肯定是一种协议。让我们来看一个例子。 例如,RoR 提供了一个非常有用的 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
如果你想在一个 Optional 的 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 进行实现。然后你扩展了 Optional 类型,同时要求 wrapped 类型为 StringProvider,并在其中添加了 isBlank 方法。 Swift 3.1 允许你扩展具体的类型,而不是协议: // Swift 3.1
extension Optional where Wrapped == String {
var isBlank: Bool {
return self?.isBlank ?? true
}
}
这提供了同样的功能,需要编写的代码变少了! 嵌套的泛型Swift 3.1 允许你将嵌套类型和泛型混用。设想一下(很疯狂)这个例子。比如 raywenderlich.com 某个团队的 lead 想发表一篇博客,他让一队专门的程序员去实现,以便满足网站的优质标准: 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
}
}
在外部类 Team 中嵌套了 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, 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 } } <div class="se-preview-section-delimiter"></div>
检测 Swift 版本你可以用 #if swift(>= N) 静态结构来检测 Swift 版本是否是某个特定的版本。 // Swift 3.0
<div class="se-preview-section-delimiter"></div>
#if swift(>=3.1)
func intVersion(number: Double) -> Int? {
return Int(exactly: number)
}
<div class="se-preview-section-delimiter"></div>
#elseif swift(>=3.0)
func intVersion(number: Double) -> Int {
return Int(number)
}
<div class="se-preview-section-delimiter"></div>
#endif
<div class="se-preview-section-delimiter"></div>
但是,这种方式用在某些地方比如 Swift 标准库时有一个很大的缺点。它需要编译编译每个标准库和每个所支持的老的语言版本。因为你运行的 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)
}
<div class="se-preview-section-delimiter"></div>
这个新特性提供了同样的功能,也就是说某个 intVersion 方法只会在对应的 Swift 版本下有效。此外它还允许向标准库这样的库只需要编译一次。编译会为指定兼容的版本选择对应的功能。
将非逃逸闭包转换为逃逸闭包Swift 3.0函数的闭包参数默认是非逃逸闭包 [SE-0103]。但是,这个提议那时并没有实现。在 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
}
}
<div class="se-preview-section-delimiter"></div>
这个函数同时执行了两个闭包,然后在两者都完成后返回。
如果你想将临时的逃逸闭包保存(比如真的 escaped 它们),它会是一个 bug。在未来的标准库版本中,标准库可能会在你试图调用它们时进行检查并捕获。 Swift 包管理器升级哈,期待已久的包管理器来了! 允许编辑的 Package在 Swift 3.1 中,包管理器中添加了一个新概念,允许编辑的包。[SE-0082] swift package edit 命令允许用一个已有的包为参数,然后将它转换成可编辑的包。可编辑的包会替换所有传统包在依赖图谱中的出现的地方。用 –end-edit 命令将可编辑包转换回传统 resolved 的包。 Version PinningSwift 3.1 在包管理器中添加了一个新概念,版本植入,将包依赖植入到指定版本[SE-0145]。pin 命令用于植入一个或所有依赖: $ swift package pin --all // pins all the dependencies
$ swift package pin Foo // pins Foo at current resolved version
$ swift package pin Foo --version 1.2.3 // pins Foo at 1.2.3
<div class="se-preview-section-delimiter"></div>
unpin 命令转回原有的包版本: $ swift package unpin —all
$ swift package unpin Foo
<div class="se-preview-section-delimiter"></div>
包管理器将 activie 版本的每个包的 pin 信息保存在 Packege.pins 中。如果这个文件不存在,包管理器自动根据 package manifest 中的必要信息创建文件,同时自动 pinning。 其它swift package reset 命令将包重置为干净状态,去掉依赖检查或编译文件。 swift test –parallel 命令执行并行测试。 杂项Swift 3.1 中还有几个小改变,并不太常见: 多次返回函数C 语言中能够返回两次的函数比如 vfork 和 setjmp 是无效的。它们以一种有趣的方式改变持续的控制流。因此 Swift 社区觉得应当禁止它们的使用,并引发一个编译时错误。 禁用 Auto-LinkingSwift 包管理器禁用了 C 语言 target 中的模块映射的 auto-linking 功能: // Swift 3.0
module MyCLib {
header “foo.h" link “MyCLib"
export *
}
// Swift 3.1
module MyCLib {
header “foo.h”
export *
}
结束Swift 3.1 完善了 Swift3.0 的功能,并准备将更激烈的改变方到今年稍晚的 Swift 4.0 中去。包括对泛型的改进、正则式、更符合工效学的字符串设计等等。 如果你觉得意犹未尽,请看一下Swift standard library diffs 或者官方的 Swift CHANGELOG,那里会有更多的关于这次修改的信息。或者,你可以继续等待 Swift 4.0 的到来! 如果你真的想知道 Swift 4 以后还会有什么变化,请访问 Swift 演进计划,在那里你可以获得当前正在提出的计划。如果急切地想知道对为什么某个正在评审中的提议迟迟得不到回复, 你甚至可以提出自己的提议 ;] 你喜欢或者不喜欢 Swift 3.1 的什么特性?请在下面留言。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |