Swift 3.1

開始
Swift 3.1 和 Swift 3.0 是源碼兼容的,所以如果已經使用 Edit\Convert\To Current Swift Syntax… 將項目遷移到了 Swift 3.0 的話,新功能將不會破壞我們的代碼。不過,蘋果在 Xcode 8.3 中已經拋棄了對 Swift 2.3 的支持。所以如果還沒有從 Swift 2.3 遷移過來,現在要抓緊做了!
下面會有類似 [SE-0001] 的標簽鏈接。這些是 Swift Evolution 的提案號。我列出了每個提案的鏈接,以便查看某變動完整的細節信息。我建議在 playground 上試一下我們討論的功能,以便對于所有變動都有更好的理解。
所以,啟動 Xcode,選擇 File\New\Playground…. 選擇 iOS 平臺,隨便取個名字。一邊讀一邊在 playground 中嘗試每個功能。
注意:如果需要簡單回顧一下 Swift 3.0 重點,看看這篇文章 What’s New in Swift 3 。
語言改進
首先,看看這次發布中的語言改進,包括數字類型的可失敗初始化方法(failable initializers)、新的序列函數等等。
可失敗數值轉換初始化方法
Swift 3.1 為所有數字類型 (Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double) 實現了可失敗初始化方法,要么完全成功、不損失精度,要么返回 nil [ SE-0080 ]。
這個功能很實用,例如,以安全、可還原(recoverable)的方式轉換來自外部的非確切類型數據。舉個實際的例子,我們可能會這樣處理一個學生的 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,就會成功。
注意:還有一種可替代的設計是使用拋出錯誤的初始化方法而不是可失敗的。社區選擇了可失敗的,因為它更好、更符合工程學設計。
新的序列函數
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 中,我們只需指定迭代次數(iteration count)即可遍歷斐波那契數列:

// 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:) 返回滿足某 predicate 的最長子序列。從序列的開頭開始,并且在第一個從給定閉包中返回 false 的元素處停下。
drop(while:) 做相反的操作:從第一個在閉包中返回 false 的元素開始,直到序列的結束,返回此子序列。
注意:這種情況下可以使用尾隨閉包(trailing closure):

let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}

Concrete Constrained Extensions
Swift 3.1 允許我們擴展具有 concrete type constraint 的泛型。之前不能擴展這樣的類型,因為約束必須是一個協議??匆粋€例子。
例如,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

如果想要 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 3.1

extension Optional where Wrapped == String { 
var isBlank: Bool {
  return  self?.isBlank ?? true
  }
}

與之前相同的功能,但是代碼更少!
泛型嵌套
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 > {
  // 本來的代碼  
  class BlogPost {
    // 本來的代碼
  }    
  // 本來的代碼
  let blogPost: BlogPost
 
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {    
    // 本來的代碼     
  }
}

注意:如果想學習 Swift 中的泛型,讀一讀這篇最近更新的教程 getting started with Swift generics 。
Swift 版本可用性
我們可以使用 Swift 版本的 #if swift(>= N) 靜態構造器,像這樣:

// 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 屬性 [ 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)
}

這個新功能與 intVersion 方法相同。但是,它只允許像標準庫這樣的庫被編譯一次。編譯器隨后只要選擇與對應版本兼容的功能即可。
注意:如果想學習 Swift 的 availability attributes,看看這篇教程 availability attributes in 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
  }
}

此函數同時運行兩個閉包,然后在兩個都完成之后返回。
1.f 和 g 一開始是非逃逸的,并被轉換為 escapableF 和 escapableG。
2.調用 async(execute:) 需要逃逸閉包。很幸運,我們已經搞定了。
3.運行 sync(flags: .barrier),以確保 async(execute:) 被全部完成了、之后不會再調用閉包。
4.Scope 限制了 escapableF 和 escapableG 的使用。
如果不臨時轉換為逃逸閉包,就會是一個 bug。標準庫未來應該能夠檢測到這種錯誤的調用。
Swift 包管理器的更新
啊啊啊啊,期待已久的 Swift 包管理器終于更新了!
Editable Packages
Swift 3.1 在 Swift 包管理器 中新增了 Editable Packages 概念 [ SE-0082 ]。
swift package edit 命令可以將現有包轉換為可編輯的??删庉嫷陌鼘鎿Q dependency graph 中的規范包。使用 —end-edit 命令將包管理器還原回規范解析的包。
Version Pinning
Swift 3.1 在特定版本的 Swift 包管理器 中新增了 version pinning 概念 [ SE-0145 ]。 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 命令并行執行測試。
雜項
Swift 3.1 還有一些并不屬于以上某個分類的改動:
多重返回函數
返回兩次的 C 函數(如 vfork 和 setjmp )將無法繼續使用。它們以可笑的方式改變了程序的控制流程。所以 Swift 社區已經決定禁止使用它們,現在直接會導致編譯時錯誤。
禁用 Auto-Linking
Swift 包管理器 禁用了 C 語言 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 優化了一些 Swift 3.0 的功能,為將在今年推出的 Swift 4.0 中捆綁的重大改動做準備。包括對泛型的巨大改進,正則表達式,更符合工程學的字符串設計等等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容