Swift 4.1 更新指北(譯)

一、概述

Swift 4.1 是 Swift 4 的第一個小版本更新,主要包括一些很實用的改進,例如,自動合成 Equatable 和 Hashable,協(xié)議條件約束,檢測模擬器環(huán)境等等。例子工程地址

二、自動合成 Equatable 和 Hashable

Equatable 協(xié)議允許 Swfit 中相同類型的兩個實例之前的比較。當我們寫 5 == 5 的時候,Swift 之所以能夠理解是因為 Int 遵守 Equtable 協(xié)議,意味著它實現(xiàn)了一個 == 函數(shù)描述兩個 Int 之間的關(guān)系。

然而,實現(xiàn) Equatable有點蛋疼,看下面的代碼:

struct Person {
    var name: String
}

如果你有兩個 Person 實例,并且想要確保他們的一致性,需要比較他們的所有屬性,具體如下:

// Swift 4.0 的實現(xiàn)
struct Person: Equatable {
    var name: String

    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name
    }
}
// Swift 4.1 實現(xiàn)
struct Person: Equatable {
    var name: String
}

上面的代碼讀起來很枯燥,寫起來更蛋疼。比較幸運的是,Swift 4.1 能夠自動合成 Equatable 協(xié)議中約定的方法,也就是自動生成 ==方法,方法中會比較兩個對象間的所有屬性是否相等。現(xiàn)在你只需要在指定的類型上添加 遵守Equatable 協(xié)議,其他的操作 Swift 會自動完成。

當然,如果你想你也可以自己實現(xiàn) == 。例如,如果你的類型有一個標記是否唯一的 id,你需要自己寫 == 來只比較這個值,而不是讓 Swift 來完成所有其他的工作。

Swift 4.1 也給 Hashable 協(xié)議提供了自動合成的支持,意味著會自動合成 hashValue 屬性。Hashable 通常情況下實現(xiàn)比較蛋疼,因為需要返回一個唯一的(或者大多數(shù)情況下唯一的)hash 值。這一點很重要,因為它可以使對象作為字典的 keys并且存儲在 Set中。

// Swift 4.0 實現(xiàn)
struct Person: Hashable {
    var name: String
    var hashValue: Int {
        return name.hashValue
    }

    static func ==(lhs: HashPerson, rhs: HashPerson) -> Bool {
        return lhs.name == rhs.name
    }
}
// Swift 4.1 實現(xiàn)
struct Person: Hashable {
    var name: String
}

雖然大多數(shù)情況下不用自己實現(xiàn),但是如果你想做些特別的事情的時候也可以自己實現(xiàn)。

注:現(xiàn)在我們?nèi)匀恍枰岊愋妥袷貐f(xié)議,自動合成需要類型的所有屬性都分別遵守了 Equatable 或者 Hashable 協(xié)議。

更多信息,請參照 Swift Evolution proposal SE-0185

三、Codable Key 編解碼策略優(yōu)化

在 Swift 4.0 中使用 Codable 協(xié)議一個常見問題就是,JSON 中使用蛇形命名法作為 key 的名字,而 Swift 中使用駝峰命名法。Codable 不能夠理解兩種命名的差別,必須創(chuàng)建自定義的 CodingKeys 枚舉來解決這個問題。

基于上面的原因,Swift 4.1 中引入了 keyDecodingStrategy 屬性。默認為 .useDefaultKeys ,直接映射 JSON 名字到Swift屬性。可以使用 .convertFromSnakeCase 來讓 Codable 處理名字轉(zhuǎn)換。

let decoder = JSONDecoder()

do {
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let macs = try decoder.decode([Mac].self, from: jsonData)
    print(macs)
} catch {
    print(error.localizedDescription)
}

反之,如果你想將遵守 Codable 協(xié)議的 struct 類型轉(zhuǎn)成 JSONStruct 的屬性是駝峰命名的,轉(zhuǎn)成的JSON是蛇形命名的,設(shè)置 keyEncodingStrategy.convertToSnakeCase

let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let encoded = try encoder.encode(someObject)

四、協(xié)議的條件遵守

Swift 4.1 實現(xiàn)了 SE-0143 提案,引入了條件遵守協(xié)議。具體為只有當滿足指定條件的時候,類型才能遵守協(xié)議。

舉例來講,我們現(xiàn)在聲明一個可以用來買東西的協(xié)議 Purchaseable

protocol Purchaseable {
   func buy()
}

現(xiàn)在可以定義一個 Book 結(jié)構(gòu)體,遵守 Purchaseable協(xié)議,在買一本書的時候打印消息。

struct Book: Purchaseable {
   func buy() {
      print("You bought a book")
   }
}

這個場景很簡單,讓我們更進一步。如果這個用戶有一個裝滿書籍的籃子,并且想把籃子里所有的書全都買下來。我們當然可以遍歷數(shù)組,然后調(diào)用每本書的 buy 方法。但是更好的方式是給 Array 寫個 Extension 遵守 Purchaseable 協(xié)議,然后實現(xiàn)協(xié)議 buy 方法,調(diào)用每個 Elementbuy 方法。

基于上面的原因,Swift 4.1 引入了 Conditional Conformances。如果我們嘗試拓展數(shù)組,會有一定的副作用。例如會給一個字符串數(shù)組添加 buy 方法,而字符串沒有 buy 方法供我們調(diào)用。

Swift 4.1 可以實現(xiàn)只有當數(shù)組中的元素是遵守 Purchaseable,數(shù)組才能遵守 Purchaseable 協(xié)議。

extension Array: Purchaseable where Element: Purchaseable {
   func buy() {
      for item in self {
         item.buy()
      }
   }
}

如你所見,協(xié)議的條件遵守,讓我們以更簡潔的方式給拓展添加協(xié)議支持。

同樣的,協(xié)議的條件遵守也使我們的 swift 代碼更加簡單和安全,并且我們也不需要做些額外的工作。例如,創(chuàng)建兩個可選字符串的數(shù)組并且比較它們是否相等。

ar left: [String?] = ["Andrew", "Lizzie", "Sophie"]
var right: [String?] = ["Charlotte", "Paul", "John"]
left == right

上面的例子看起來不那么重要,但是 Swift 4.0 上面的語法不能編譯,String[String]Equatable,但是 [String?] 不是。

Swift 4.1 中的協(xié)議的條件約束指的是只要滿足指定的條件,就可以遵守協(xié)議。上面的例子中,如果數(shù)組中的元素是遵守 Equatble 的,那么數(shù)組就是遵守 Equatable 協(xié)議。所以,上面的代碼在 Swift 4.1 上可以編譯通過。

協(xié)議的條件遵守也適用于 Codable 協(xié)議,并且使代碼變得更加安全。

import Foundation

struct Person {
   var name = "Taylor"
}

var people = [Person()]
var encoder = JSONEncoder()
// try encoder.encode(people)

如果將 encoder.encode(people) 的注釋打開,在 Swift 4.1 中編譯不通過,因為試圖 encode 一個不遵守 Codable 協(xié)議的類型。然而,這段代碼在 swift 4.0 上面是可以編譯通過的,但是因為 Person 不遵守 Codable 協(xié)議會導致在運行時崩潰。

很明顯,大家都不想要運行時崩潰。幸運的是,Swift 4.1 使用協(xié)議的條件遵守幫我們清除了這個障礙,OptionalArrayDictionarySet 只有在他們的內(nèi)容遵守 Codable 協(xié)議的時候,自身才遵守協(xié)議,所以上面的代碼在 Swift 4.1 會編譯不過。

五、關(guān)聯(lián)類型的遞歸約束

Swift 4.1 實現(xiàn)了 SE-0157 提案,增強了協(xié)議內(nèi)部使用關(guān)聯(lián)類型的限制。現(xiàn)在可以給關(guān)聯(lián)類型創(chuàng)建一個遞歸的約束,就是關(guān)聯(lián)類型可以用自身所在協(xié)議來約束自己。

我們以技術(shù)公司的管理層級來闡述這個問題,在一個公司,每一個雇員都有一個上司,每個上司必須有一個以上的下屬。我們以一個 Employee 協(xié)議來表明這樣的關(guān)系:

protocol Employee {
   associatedtype Manager: Employee
   var manager: Manager? { get set }
}

盡管這是一個不言而喻的關(guān)系,但是 Swift 4.0 上這段代碼卻編譯不過,因為在協(xié)議內(nèi)部使用了自己。

感謝這個新特性,我們可以模擬一個包含三種團隊角色的技術(shù)公司,初級開發(fā)工程師、高級開發(fā)工程師和董事會成員。

class BoardMember: Employee {
   var manager: BoardMember?
}

class SeniorDeveloper: Employee {
   var manager: BoardMember?
}

class JuniorDeveloper: Employee {
   var manager: SeniorDeveloper?
}

注:這邊用 Class 而不是 Struct,是因為 BoardMember 里面包含一個 BoardMember,如果用結(jié)構(gòu)體會形成無窮大的結(jié)構(gòu)體。如果這里面有一個 Class ,我個人傾向于使用全部采用 Class 來保持一致。如果你想要使用結(jié)構(gòu)體,可以把 JuniorDeveloperSeniorDeveloper 設(shè)置成結(jié)構(gòu)體。

六、模塊引入檢測

Swift 4.1 實現(xiàn)了 SE-0075 提案,引入了一個新的 canImport 條件來幫助我們在編譯期檢測一個指定的模塊能否被導入。

這個特性對跨平臺的代碼很有用,例如你的代碼在 macOSiOS 行為不一樣,或者你需要 Linux 的功能。

#if canImport(SpriteKit)
   // this will be true for iOS, macOS, tvOS, and watchOS
#else
   // this will be true for other platforms, such as Linux
#endif

之前我們必須通過判斷平臺信息來處理這種情況。

#if !os(Linux)
   // Matches macOS, iOS, watchOS, tvOS, and any other future platforms
#endif

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
   // Matches only Apple platforms, but needs to be kept up to date as new platforms are added
#endif

新特性 canImport 讓我們更好的關(guān)注功能,而不是當前編譯的平臺,避免了很多蛋疼的問題。

七、模擬器環(huán)境檢測

Swift 4.1 實現(xiàn)了 SE-0190 提案,引入了 targetEnvironment 條件,幫助我們更好的區(qū)分模擬器和真機。現(xiàn)在 targetEnvironment 只有一個值 simulator,當是模擬器設(shè)備的時候,返回 true

#if targetEnvironment(simulator)
   // code for the simulator here
#else
   // code for real devices here
#endif

當代碼用來處理類似于從攝像頭讀取數(shù)據(jù)或者訪問陀螺儀數(shù)據(jù)等模擬器不支持的功能的時候,這個條件判斷很有用。舉個例子,從攝像頭選擇照片,如果是真機,創(chuàng)建和配置 UIImagePickerController()方法 ,如果是模擬器,從 Bundle 中讀取一張圖片。

import UIKit

class TestViewController: UIViewController, UIImagePickerControllerDelegate {
   // a method that does some sort of image processing
   func processPhoto(_ img: UIImage) {
       // process photo here
   }

   // a method that loads a photo either using the camera or using a sample
   func takePhoto() {
      #if targetEnvironment(simulator)
         // we're building for the simulator; use the sample photo
         if let img = UIImage(named: "sample") {
            processPhoto(img)
         } else {
            fatalError("Sample image failed to load")
         }
      #else
         // we're building for a real device; take an actual photo
         let picker = UIImagePickerController()
         picker.sourceType = .camera
         vc.allowsEditing = true
         picker.delegate = self
         present(picker, animated: true)
      #endif
   }

   // this is called if the photo was taken successfully
   func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
      // hide the camera
      picker.dismiss(animated: true)

      // attempt to retrieve the photo they took
      guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else {
         // that failed; bail out
         return
      }

      // we have an image, so we can process it
      processPhoto(image)
   }
}

八、FlatMap 部分場景更名 CompactMap

FlatMap 在 Swift 4.0 中很有用,特別是在轉(zhuǎn)換集合中的對象,并且移除其中的 nil 對象的時候。Swift 提案 SE-0187 中有對這部分內(nèi)容更改的說明,在 Swift 4.1 中 flatMap 為了語義更加清晰,已經(jīng)更名成 compactMap

let array = ["1", "2", "Fish"]
let numbers = array.compactMap { Int($0) }

上面例子的結(jié)果是 [1, 2]

九、展望 Swift 5

引入?yún)f(xié)議的條件遵守已經(jīng)使 Swift 團隊提升穩(wěn)定性的同時,移除了大量代碼,自動合成 EquatableHashable 的支持也使我們開發(fā)更加便捷。其他一些在開發(fā)或者在 Review 的提案,包括 SE-0192: Non-Exhaustive Enums, SE-0194: Derived Collection of Enum Cases,和 SE-0195: Dynamic Member Lookupclick here to learn more about new Swift features coming in 2018。同這些新特性一樣重要的是,蘋果計劃在今年實現(xiàn) Swift 的 ABI 穩(wěn)定,期待??????。

十、感謝原作者 Paul Hudson原文地址

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

推薦閱讀更多精彩內(nèi)容