開始
Swift 3.1 和 Swift 3.0 是源碼兼容的,所以如果已經(jīng)使用 Edit\Convert\To Current Swift Syntax… 將項(xiàng)目遷移到了 Swift 3.0 的話,新功能將不會(huì)破壞我們的代碼。不過,蘋果在 Xcode 8.3 中已經(jīng)拋棄了對(duì) Swift 2.3 的支持。所以如果還沒有從 Swift 2.3 遷移過來,現(xiàn)在要抓緊做了!
下面會(huì)有類似 [SE-0001] 的標(biāo)簽鏈接。這些是 Swift Evolution 的提案號(hào)。我列出了每個(gè)提案的鏈接,以便查看某變動(dòng)完整的細(xì)節(jié)信息。我建議在 playground 上試一下我們討論的功能,以便對(duì)于所有變動(dòng)都有更好的理解。
所以,啟動(dòng) Xcode,選擇 File\New\Playground…. 選擇 iOS 平臺(tái),隨便取個(gè)名字。一邊讀一邊在 playground 中嘗試每個(gè)功能。
注意:如果需要簡(jiǎn)單回顧一下 Swift 3.0 重點(diǎn),看看這篇文章 What’s New in Swift 3 。
語(yǔ)言改進(jìn)
首先,看看這次發(fā)布中的語(yǔ)言改進(jìn),包括數(shù)字類型的可失敗初始化方法(failable initializers)、新的序列函數(shù)等等。
可失敗數(shù)值轉(zhuǎn)換初始化方法
Swift 3.1 為所有數(shù)字類型 (Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double) 實(shí)現(xiàn)了可失敗初始化方法,要么完全成功、不損失精度,要么返回 nil [ SE-0080 ]。
這個(gè)功能很實(shí)用,例如,以安全、可還原(recoverable)的方式轉(zhuǎn)換來自外部的非確切類型數(shù)據(jù)。舉個(gè)實(shí)際的例子,我們可能會(huì)這樣處理一個(gè)學(xué)生的 JSON 數(shù)組:
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)]
我們?cè)?Student 類的指定可失敗初始化方法中用可失敗構(gòu)造器將 grade 屬性從 Double 轉(zhuǎn)換為 Int,就像這樣:
let grade = Int(exactly: gradeDouble)
如果 gradeDouble 是小數(shù),例如 6.33,就會(huì)失敗。如果可以用 Int 來表示,例如 6.0,就會(huì)成功。
注意:還有一種可替代的設(shè)計(jì)是使用拋出錯(cuò)誤的初始化方法而不是可失敗的。社區(qū)選擇了可失敗的,因?yàn)樗?、更符合工程學(xué)設(shè)計(jì)。
新的序列函數(shù)
Swift 3.1 為標(biāo)準(zhǔn)庫(kù)的 Sequence 協(xié)議增加了兩個(gè)新函數(shù),用于數(shù)據(jù)過濾:prefix(while:) 和 drop(while:) [ SE-0045 ]。
考慮斐波那契無限數(shù)列:
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 中,我們只需指定迭代次數(shù)(iteration count)即可遍歷斐波那契數(shù)列:
// 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:) 來獲取位于序列兩個(gè)給定值之間所有的元素,像這樣:
// 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 的最長(zhǎng)子序列。從序列的開頭開始,并且在第一個(gè)從給定閉包中返回 false 的元素處停下。
drop(while:) 做相反的操作:從第一個(gè)在閉包中返回 false 的元素開始,直到序列的結(jié)束,返回此子序列。
注意:這種情況下可以使用尾隨閉包(trailing closure):
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}
Concrete Constrained Extensions
Swift 3.1 允許我們擴(kuò)展具有 concrete type constraint 的泛型。之前不能擴(kuò)展這樣的類型,因?yàn)榧s束必須是一個(gè)協(xié)議。看一個(gè)例子。
例如,Ruby On Rails 提供了一個(gè)非常實(shí)用的方法 isBlank 用于檢查用戶輸入。在 Swift 3.0 中我們會(huì)將其實(shí)現(xiàn)為 String 擴(kuò)展中的計(jì)算屬性:
// 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 計(jì)算屬性,在 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
我們創(chuàng)建了一個(gè) StringProvider 協(xié)議供 String 采用。當(dāng)拆包類型是 StringProvider 的時(shí)候使用它擴(kuò)展 Optional,添加 isBlank 方法。
Swift 3.1 可以不用這樣的協(xié)議來擴(kuò)展 concrete type:
// Swift 3.1
extension Optional where Wrapped == String {
var isBlank: Bool {
return self?.isBlank ?? true
}
}
與之前相同的功能,但是代碼更少!
泛型嵌套
Swift 3.1 讓我們可以混合使用泛型和類型嵌套。練習(xí)一下,看看這個(gè)(不是很難的)例子。如果某個(gè) raywenderlich.com 的團(tuán)隊(duì)領(lǐng)導(dǎo)想要在博客上發(fā)一篇文章,他會(huì)找專門的開發(fā)者團(tuán)隊(duì)來處理這個(gè)問題,以保證文章的質(zhì)量:
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
}
}
我們把內(nèi)部類 BlogPost 嵌套在對(duì)應(yīng)的外部類 Team 中,這兩個(gè)類都是泛型。目前團(tuán)隊(duì)在尋找已發(fā)布的教程和文章時(shí)需要這樣做:
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()))
但實(shí)際上可以簡(jiǎn)化這里的代碼。如果嵌套的內(nèi)部類型用了外部的泛型,它就默認(rèn)繼承了父類的類型。因此我們不需要聲明,只要這樣寫就可以了:
class Team<t > {
// 本來的代碼
class BlogPost {
// 本來的代碼
}
// 本來的代碼
let blogPost: BlogPost
init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
// 本來的代碼
}
}
注意:如果想學(xué)習(xí) Swift 中的泛型,讀一讀這篇最近更新的教程 getting started with Swift generics 。
Swift 版本可用性
我們可以使用 Swift 版本的 #if swift(>= N) 靜態(tài)構(gòu)造器,像這樣:
// 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 標(biāo)準(zhǔn)庫(kù)這樣的東西時(shí),這種方法有一個(gè)很大的缺點(diǎn)。它需要為每個(gè)舊語(yǔ)言版本編譯標(biāo)準(zhǔn)庫(kù)。因?yàn)槿绻褂?Swift 3.0 的行為,則需要使用針對(duì)該版本編譯的標(biāo)準(zhǔn)庫(kù)。如果使用 3.1 版本的標(biāo)準(zhǔn)庫(kù),就根本沒有正確的代碼。
所以,Swift 3.1 擴(kuò)展了 @available 屬性 [ SE-0141 ]:
// 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)
}
這個(gè)新功能與 intVersion 方法相同。但是,它只允許像標(biāo)準(zhǔn)庫(kù)這樣的庫(kù)被編譯一次。編譯器隨后只要選擇與對(duì)應(yīng)版本兼容的功能即可。
注意:如果想學(xué)習(xí) Swift 的 availability attributes,看看這篇教程 availability attributes in Swift。
將非逃逸閉包轉(zhuǎn)換為逃逸閉包
Swift 3.0 把函數(shù)的閉包參數(shù)改為默認(rèn)非逃逸 [SE-0103]。但是,該提案當(dāng)時(shí)并沒有被完全實(shí)現(xiàn)。在 Swift 3.1 中,可以使用新的輔助函數(shù) withoutActuallyEscaping() 臨時(shí)轉(zhuǎn)換非逃逸閉包。
為什么要這么做?雖然可能永遠(yuǎn)都不會(huì)用到,但還是考慮一下提案提到的這個(gè)例子。
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
}
}
此函數(shù)同時(shí)運(yùn)行兩個(gè)閉包,然后在兩個(gè)都完成之后返回。
1.f 和 g 一開始是非逃逸的,并被轉(zhuǎn)換為 escapableF 和 escapableG。
2.調(diào)用 async(execute:) 需要逃逸閉包。很幸運(yùn),我們已經(jīng)搞定了。
3.運(yùn)行 sync(flags: .barrier),以確保 async(execute:) 被全部完成了、之后不會(huì)再調(diào)用閉包。
4.Scope 限制了 escapableF 和 escapableG 的使用。
如果不臨時(shí)轉(zhuǎn)換為逃逸閉包,就會(huì)是一個(gè) bug。標(biāo)準(zhǔn)庫(kù)未來應(yīng)該能夠檢測(cè)到這種錯(cuò)誤的調(diào)用。
Swift 包管理器的更新
啊啊啊啊,期待已久的 Swift 包管理器終于更新了!
Editable Packages
Swift 3.1 在 Swift 包管理器 中新增了 Editable Packages 概念 [ SE-0082 ]。
swift package edit 命令可以將現(xiàn)有包轉(zhuǎn)換為可編輯的??删庉嫷陌鼘?huì)替換 dependency graph 中的規(guī)范包。使用 —end-edit 命令將包管理器還原回規(guī)范解析的包。
Version Pinning
Swift 3.1 在特定版本的 Swift 包管理器 中新增了 version pinning 概念 [ SE-0145 ]。 pin 命令會(huì)像這樣固定一個(gè)或多個(gè)依賴:
$ swift package pin --all // pin 所有依賴
$ swift package pin Foo // 把 Foo pin 在當(dāng)前解析版本
$ swift package pin Foo --version 1.2.3 // 把 Foo pin 在 1.2.3
使用 unpin 命令恢復(fù)到以前的包版本:
$ swift package unpin —all
$ swift package unpin Foo
包管理器在 Package.pins 中存儲(chǔ)每個(gè)包的活躍版本 pin 信息。如果文件不存在,包管理器則會(huì)按照包 manifest 中指定的要求自動(dòng)創(chuàng)建該文件,這是 automatic pinning 過程的一部分。
其它
swift package reset 命令將包重置為干凈狀態(tài),不會(huì)檢出當(dāng)前的依賴關(guān)系或 build artifact。
還有,使用 swift test --parallel 命令并行執(zhí)行測(cè)試。
雜項(xiàng)
Swift 3.1 還有一些并不屬于以上某個(gè)分類的改動(dòng):
多重返回函數(shù)
返回兩次的 C 函數(shù)(如 vfork 和 setjmp )將無法繼續(xù)使用。它們以可笑的方式改變了程序的控制流程。所以 Swift 社區(qū)已經(jīng)決定禁止使用它們,現(xiàn)在直接會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤。
禁用 Auto-Linking
Swift 包管理器 禁用了 C 語(yǔ)言 target module maps 的 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 優(yōu)化了一些 Swift 3.0 的功能,為將在今年推出的 Swift 4.0 中捆綁的重大改動(dòng)做準(zhǔn)備。包括對(duì)泛型的巨大改進(jìn),正則表達(dá)式,更符合工程學(xué)的字符串設(shè)計(jì)等等。