1. 程式人生 > >Swift 4.1 的新特性

Swift 4.1 的新特性

  • 蘋果公司在 3.29 正式釋出了正式版的Xcode 9.3Swift 4.1, 讓我們看看Swift 4.1帶來了哪些新功能和新亮點
  • 測試需要Xcode9.3, 請確保你的Xcode是最新的9.3版本
  • Swift 4.1Swift 4.0是原始碼相容的,所以如果你已經使用Xcode中的Swift Migrator將你的專案遷移到Swift 4.0,那麼新特性不會破壞你的程式碼
  • 下面在Xcode9.3下新建一個Playground工程, 測試我們的程式碼

條件一致性(Conditional Conformance)

  • 條件一致性使型別引數滿足特定條件的泛型型別的協議一致性 [ SE-0143 ]
  • 在Swift 4中,如果陣列、字典或者可選型別的元素型別遵循Equatable,你是可以在陣列之間、字典之間和可選型別之間進行比較的, 如下示例:
// Int型別的陣列
let arr1 = [1, 2, 3]
let arr2 = [1, 2, 3]
print(arr1 == arr2)

// 比較value值為Int的字典
let dic1 = ["age": 19, "score": 60]
let dic2 = ["age": 19, "score": 60]
print(dic1 == dic2)

// 比較Int?
let age1 = dic1["age"]
let age2 = dic2["age"
] print(age1 == age2) /// 以上輸出結果都是: true

這裡如果我們把Int都換成Int?型別, 在Swift4.0中是不能編譯通過的, 如下:

// Int型別的陣列
let arr1: [Int?] = [1, 2, 3]
let arr2: [Int?] = [1, 2, 3]
print(arr1 == arr2)

// 比較value值為Int的字典
let dic1: [String: Int?] = ["age": 19, "score": 60]
let dic2: [String: Int?] = ["age": 19, "score": 60]
print(dic1 == dic2)

// 比較Int?
let age1 = dic1["age"] let age2 = dic2["age"] print(age1 == age2)
  • 在這些例項中, 我們用==測試相等性, 在Swift4.0中, Int型別遵循Equatable協議, 可以比較, 但是Int?型別卻沒有遵循Equatable協議
  • 但是在Swift4.1中, 完美的解決了這個問題, 上述程式碼可比那已通過, 且都輸出: true
  • Swift 4.0[Set<Int>]之間可以直接對比,但是[[Int]]不能。現在Swift 4.1中,[[Int]]也能直接對比
  • 總的來說,Swift 4.1ArrayDictionaryOptional,只要他們的元素都遵循了EquatableHashable,那麼他們也遵循EquatableHashable

合成 EquatableHashable

  • 如果物件相等,則這兩個物件的 hash 值一定相等
  • 如果兩個物件 hash 值相等,這兩個物件不一定相等。
  • SwiftHashable 一定是 Equatable,因為前者繼承了後者。
    Swift 4中,若遵循Equatable協議的時候,我們必須實現Equatable協議的==方法, Equatable協議如下:
public protocol Equatable {

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (lhs: Self, rhs: Self) -> Bool
}

在Swift4.0中, 必須實現Equatable協議的方法

struct Name: Equatable {
    var name1 = "name1"
    var name2 = "name2"

    static func == (lhs: Name, rhs: Name) -> Bool {
        return lhs.name1 == rhs.name1 &&
            lhs.name2 == rhs.name2
    }
}

Swift 4.1,只需要加上Equatable即可, 不需要實現任何協議方法

struct Name: Equatable {
    var name1 = "name1"
    var name2 = "name2"

}

JSON編碼時支援Camel CaseSnake Case之間的轉換

  • Swift 4.0引入了Codable,但是有個麻煩的問題:如果JSON資料的key命名格式是snake_case的話,我們必須建立自己的CodingKeys來告訴蘋果怎麼轉換。在Swift 4.0
  • 但是在Swift 4.1中,蘋果給JSONDecoder引入了一個屬性keyDecodingStrategy;對應的JSONEncoder引入了一個屬性keyEncodingStrategy。這樣我們就不需要設定定義CodingKeys了。只需要在decoding的時候把keyDecodingStrategy設定為.convertFromSnakeCase;在encoding的時候把keyEncodingStrategy設定為.convertToSnakeCase
  • 下面是分別針對陣列/字典/集合的解析形式
struct Student: Codable, Hashable {
  let firstName: String
  let averageGrade: Int
}

let cosmin = Student(firstName: "Cosmin", averageGrade: 10)
let george = Student(firstName: "George", averageGrade: 9)
let encoder = JSONEncoder()

// Encode an Array of students
let students = [cosmin, george]
do {
  try encoder.encode(students)
} catch {
  print("Failed encoding students array: \(error)")
}

// Encode a Dictionary with student values
let studentsDictionary = ["Cosmin": cosmin, "George": george]
do {
  try encoder.encode(studentsDictionary)
} catch {
  print("Failed encoding students dictionary: \(error)")
}

// Encode a Set of students
let studentsSet: Set = [cosmin, george]
do {
  try encoder.encode(studentsSet)
} catch {
  print("Failed encoding students set: \(error)")
}

// Encode an Optional Student
let optionalStudent: Student? = cosmin
do {
  try encoder.encode(optionalStudent)
} catch {
  print("Failed encoding optional student: \(error)")
}

Hashable Index Types(雜湊化索引)

擴充套件 Key-path 表示式在標準庫中的使用範圍。讓標準庫中所有的索引型別都符合 Hashable 協議,這樣,[Int]String 和所有其它標準集合使用 key-path 下標時,表現都是一樣的

let swiftString2 = "one two three"
let charact1 = \String.[swiftString2.startIndex]
print(swiftString2[keyPath: charact1])

let arr = [1, 2, 3, 4]
let value2 = \[Int].[1]
print(arr[keyPath: value2])

//輸出結果:
o
2

compactMap的用法

Swift 4.0中,我們經常使用flatMap來過濾nil,也可以進行降維操作, 詳情可參考Swift函數語言程式設計之高階用法

let arr = [1, 2, nil, 3, 4, nil]
let arr1 = arr.flatMap({ $0 })
print(arr1)

//這樣使用會有類似的警告
'flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value 
Use 'compactMap(_:)' instead

//Swift4.1中的用法
let arr = [1, 2, nil, 3, 4, nil]
let arr2 = arr.compactMap({ $0 })
print(arr2)

主要是因為在Swift4.0flatMap有很多過載, 可能會引起歧義, 所以在Swift4.1中把flatMap重新命名為compactMap

除了協議中的 weak 和 unowned。

  • 當你在Tune協議中定義了兩個屬性keypitch, pitch可能為nil, 所以你在協議中可以用weak修飾
  • 但是如果在協議本身中定義的話,weakunowned都沒有實際意義, 所以在Swift4.1中就已經去掉了這些關鍵字, 並且在協議中使用這些關鍵字將會爆出警告
class Key {}
class Pitch {}

// Swift 4
protocol Tune {
    unowned var key: Key { get set }
    weak var pitch: Pitch? { get set }
}
// Swift 4.1
protocol Tune {
    var key: Key { get set }
    var pitch: Pitch? { get set }
}

UnsafeMutableBufferPointer的改變

//Swift4.0
let buffer = UnsafeMutableBufferPointer<Int>(start: UnsafeMutablePointer<Int>.allocate(capacity: 10), 
                                             count: 10)
let mutableBuffer = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(mutating: buffer.baseAddress), 
                                               count: buffer.count)

//Swift4.1
let buffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 10)
let mutableBuffer = UnsafeMutableBufferPointer(mutating: UnsafeBufferPointer(buffer))

相對Swift4.0的改變, Swift4.1這點改變鍵值太微不足道了, 傳說中Swift5API會趨於穩定, 但是估計改變可能也是非常大的, 坐等Swift5釋出…

參考文件