Swift 3.1新改動

weixin_33866037發表於2017-03-31

概述

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 的新內容 感謝翻譯?

相關文章