一指厌、概述
Swift 4.1 是 Swift 4 的第一個(gè)小版本更新,主要包括一些很實(shí)用的改進(jìn)馍驯,例如,自動(dòng)合成 Equatable 和 Hashable玛痊,協(xié)議條件約束汰瘫,檢測(cè)模擬器環(huán)境等等。例子工程地址
二擂煞、自動(dòng)合成 Equatable 和 Hashable
Equatable
協(xié)議允許 Swfit 中相同類(lèi)型的兩個(gè)實(shí)例之前的比較混弥。當(dāng)我們寫(xiě) 5 == 5
的時(shí)候,Swift 之所以能夠理解是因?yàn)?Int
遵守 Equtable
協(xié)議对省,意味著它實(shí)現(xiàn)了一個(gè) ==
函數(shù)描述兩個(gè) Int 之間的關(guān)系蝗拿。
然而,實(shí)現(xiàn) Equatable
有點(diǎn)蛋疼蒿涎,看下面的代碼:
struct Person {
var name: String
}
如果你有兩個(gè) Person 實(shí)例哀托,并且想要確保他們的一致性,需要比較他們的所有屬性劳秋,具體如下:
// Swift 4.0 的實(shí)現(xiàn)
struct Person: Equatable {
var name: String
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
}
// Swift 4.1 實(shí)現(xiàn)
struct Person: Equatable {
var name: String
}
上面的代碼讀起來(lái)很枯燥萤捆,寫(xiě)起來(lái)更蛋疼。比較幸運(yùn)的是俗批,Swift 4.1 能夠自動(dòng)合成 Equatable
協(xié)議中約定的方法俗或,也就是自動(dòng)生成 ==
方法,方法中會(huì)比較兩個(gè)對(duì)象間的所有屬性是否相等∷晖現(xiàn)在你只需要在指定的類(lèi)型上添加 遵守Equatable
協(xié)議辛慰,其他的操作 Swift 會(huì)自動(dòng)完成。
當(dāng)然干像,如果你想你也可以自己實(shí)現(xiàn) ==
帅腌。例如,如果你的類(lèi)型有一個(gè)標(biāo)記是否唯一的 id
麻汰,你需要自己寫(xiě) ==
來(lái)只比較這個(gè)值速客,而不是讓 Swift
來(lái)完成所有其他的工作。
Swift 4.1 也給 Hashable
協(xié)議提供了自動(dòng)合成的支持五鲫,意味著會(huì)自動(dòng)合成 hashValue
屬性溺职。Hashable
通常情況下實(shí)現(xiàn)比較蛋疼,因?yàn)樾枰祷匾粋€(gè)唯一的(或者大多數(shù)情況下唯一的)hash
值。這一點(diǎn)很重要浪耘,因?yàn)樗梢允箤?duì)象作為字典的 keys
并且存儲(chǔ)在 Set
中乱灵。
// Swift 4.0 實(shí)現(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 實(shí)現(xiàn)
struct Person: Hashable {
var name: String
}
雖然大多數(shù)情況下不用自己實(shí)現(xiàn),但是如果你想做些特別的事情的時(shí)候也可以自己實(shí)現(xiàn)七冲。
注:現(xiàn)在我們?nèi)匀恍枰岊?lèi)型遵守協(xié)議痛倚,自動(dòng)合成需要類(lèi)型的所有屬性都分別遵守了 Equatable
或者 Hashable
協(xié)議。
更多信息澜躺,請(qǐng)參照 Swift Evolution proposal SE-0185
三蝉稳、Codable Key 編解碼策略?xún)?yōu)化
- 之前寫(xiě)過(guò)一篇完整的文章來(lái)描述這個(gè)特性:Swift 4.1 improves Codable with keyDecodingStrategy
在 Swift 4.0 中使用 Codable
協(xié)議一個(gè)常見(jiàn)問(wèn)題就是,JSON
中使用蛇形命名法作為 key
的名字掘鄙,而 Swift
中使用駝峰命名法颠区。Codable
不能夠理解兩種命名的差別,必須創(chuàng)建自定義的 CodingKeys
枚舉來(lái)解決這個(gè)問(wèn)題通铲。
基于上面的原因毕莱,Swift 4.1 中引入了 keyDecodingStrategy
屬性。默認(rèn)為 .useDefaultKeys
颅夺,直接映射 JSON
名字到Swift
屬性朋截。可以使用 .convertFromSnakeCase
來(lái)讓 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
類(lèi)型轉(zhuǎn)成 JSON
,Struct
的屬性是駝峰命名的拗慨,轉(zhuǎn)成的JSON
是蛇形命名的廓八,設(shè)置 keyEncodingStrategy
為 .convertToSnakeCase
。
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let encoded = try encoder.encode(someObject)
四赵抢、協(xié)議的條件遵守
Swift 4.1 實(shí)現(xiàn)了 SE-0143 提案剧蹂,引入了條件遵守協(xié)議。具體為只有當(dāng)滿足指定條件的時(shí)候烦却,類(lèi)型才能遵守協(xié)議宠叼。
舉例來(lái)講,我們現(xiàn)在聲明一個(gè)可以用來(lái)買(mǎi)東西的協(xié)議 Purchaseable
其爵。
protocol Purchaseable {
func buy()
}
現(xiàn)在可以定義一個(gè) Book
結(jié)構(gòu)體冒冬,遵守 Purchaseable
協(xié)議,在買(mǎi)一本書(shū)的時(shí)候打印消息摩渺。
struct Book: Purchaseable {
func buy() {
print("You bought a book")
}
}
這個(gè)場(chǎng)景很簡(jiǎn)單简烤,讓我們更進(jìn)一步。如果這個(gè)用戶有一個(gè)裝滿書(shū)籍的籃子摇幻,并且想把籃子里所有的書(shū)全都買(mǎi)下來(lái)横侦。我們當(dāng)然可以遍歷數(shù)組挥萌,然后調(diào)用每本書(shū)的 buy
方法。但是更好的方式是給 Array
寫(xiě)個(gè) Extension
遵守 Purchaseable
協(xié)議丈咐,然后實(shí)現(xiàn)協(xié)議 buy
方法瑞眼,調(diào)用每個(gè) Element
的 buy
方法龙宏。
基于上面的原因棵逊,Swift 4.1 引入了 Conditional Conformances
。如果我們嘗試拓展數(shù)組银酗,會(huì)有一定的副作用辆影。例如會(huì)給一個(gè)字符串?dāng)?shù)組添加 buy
方法,而字符串沒(méi)有 buy
方法供我們調(diào)用黍特。
Swift 4.1 可以實(shí)現(xiàn)只有當(dāng)數(shù)組中的元素是遵守 Purchaseable
蛙讥,數(shù)組才能遵守 Purchaseable
協(xié)議。
extension Array: Purchaseable where Element: Purchaseable {
func buy() {
for item in self {
item.buy()
}
}
}
如你所見(jiàn)灭衷,協(xié)議的條件遵守次慢,讓我們以更簡(jiǎn)潔的方式給拓展添加協(xié)議支持。
同樣的翔曲,協(xié)議的條件遵守也使我們的 swift 代碼更加簡(jiǎn)單和安全迫像,并且我們也不需要做些額外的工作。例如瞳遍,創(chuàng)建兩個(gè)可選字符串的數(shù)組并且比較它們是否相等闻妓。
ar left: [String?] = ["Andrew", "Lizzie", "Sophie"]
var right: [String?] = ["Charlotte", "Paul", "John"]
left == right
上面的例子看起來(lái)不那么重要,但是 Swift 4.0 上面的語(yǔ)法不能編譯掠械,String
和 [String]
是 Equatable
由缆,但是 [String?]
不是。
Swift 4.1 中的協(xié)議的條件約束指的是只要滿足指定的條件猾蒂,就可以遵守協(xié)議均唉。上面的例子中,如果數(shù)組中的元素是遵守 Equatble
的肚菠,那么數(shù)組就是遵守 Equatable
協(xié)議浸卦。所以,上面的代碼在 Swift 4.1 上可以編譯通過(guò)案糙。
協(xié)議的條件遵守也適用于 Codable
協(xié)議限嫌,并且使代碼變得更加安全。
import Foundation
struct Person {
var name = "Taylor"
}
var people = [Person()]
var encoder = JSONEncoder()
// try encoder.encode(people)
如果將 encoder.encode(people)
的注釋打開(kāi)时捌,在 Swift 4.1 中編譯不通過(guò)怒医,因?yàn)樵噲D encode
一個(gè)不遵守 Codable
協(xié)議的類(lèi)型。然而奢讨,這段代碼在 swift 4.0 上面是可以編譯通過(guò)的稚叹,但是因?yàn)?Person
不遵守 Codable
協(xié)議會(huì)導(dǎo)致在運(yùn)行時(shí)崩潰。
很明顯,大家都不想要運(yùn)行時(shí)崩潰扒袖。幸運(yùn)的是塞茅,Swift 4.1 使用協(xié)議的條件遵守幫我們清除了這個(gè)障礙,Optional
季率、Array
野瘦、Dictionary
和 Set
只有在他們的內(nèi)容遵守 Codable
協(xié)議的時(shí)候,自身才遵守協(xié)議飒泻,所以上面的代碼在 Swift 4.1 會(huì)編譯不過(guò)鞭光。
五、關(guān)聯(lián)類(lèi)型的遞歸約束
Swift 4.1 實(shí)現(xiàn)了 SE-0157 提案泞遗,增強(qiáng)了協(xié)議內(nèi)部使用關(guān)聯(lián)類(lèi)型的限制《栊恚現(xiàn)在可以給關(guān)聯(lián)類(lèi)型創(chuàng)建一個(gè)遞歸的約束,就是關(guān)聯(lián)類(lèi)型可以用自身所在協(xié)議來(lái)約束自己史辙。
我們以技術(shù)公司的管理層級(jí)來(lái)闡述這個(gè)問(wèn)題汹买,在一個(gè)公司,每一個(gè)雇員都有一個(gè)上司聊倔,每個(gè)上司必須有一個(gè)以上的下屬晦毙。我們以一個(gè) Employee
協(xié)議來(lái)表明這樣的關(guān)系:
protocol Employee {
associatedtype Manager: Employee
var manager: Manager? { get set }
}
盡管這是一個(gè)不言而喻的關(guān)系,但是 Swift 4.0 上這段代碼卻編譯不過(guò)方库,因?yàn)樵趨f(xié)議內(nèi)部使用了自己结序。
感謝這個(gè)新特性,我們可以模擬一個(gè)包含三種團(tuán)隊(duì)角色的技術(shù)公司纵潦,初級(jí)開(kāi)發(fā)工程師徐鹤、高級(jí)開(kāi)發(fā)工程師和董事會(huì)成員。
class BoardMember: Employee {
var manager: BoardMember?
}
class SeniorDeveloper: Employee {
var manager: BoardMember?
}
class JuniorDeveloper: Employee {
var manager: SeniorDeveloper?
}
注:這邊用 Class
而不是 Struct
邀层,是因?yàn)?BoardMember
里面包含一個(gè) BoardMember
返敬,如果用結(jié)構(gòu)體會(huì)形成無(wú)窮大的結(jié)構(gòu)體。如果這里面有一個(gè) Class
寥院,我個(gè)人傾向于使用全部采用 Class
來(lái)保持一致劲赠。如果你想要使用結(jié)構(gòu)體,可以把 JuniorDeveloper
和 SeniorDeveloper
設(shè)置成結(jié)構(gòu)體秸谢。
六凛澎、模塊引入檢測(cè)
Swift 4.1 實(shí)現(xiàn)了 SE-0075 提案,引入了一個(gè)新的 canImport
條件來(lái)幫助我們?cè)诰幾g期檢測(cè)一個(gè)指定的模塊能否被導(dǎo)入估蹄。
這個(gè)特性對(duì)跨平臺(tái)的代碼很有用塑煎,例如你的代碼在 macOS
和 iOS
行為不一樣,或者你需要 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
之前我們必須通過(guò)判斷平臺(tái)信息來(lái)處理這種情況最铁。
#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)注功能艘虎,而不是當(dāng)前編譯的平臺(tái)过蹂,避免了很多蛋疼的問(wèn)題姐呐。
七力细、模擬器環(huán)境檢測(cè)
Swift 4.1 實(shí)現(xiàn)了 SE-0190 提案,引入了 targetEnvironment
條件雀哨,幫助我們更好的區(qū)分模擬器和真機(jī)】牧拢現(xiàn)在 targetEnvironment
只有一個(gè)值 simulator
,當(dāng)是模擬器設(shè)備的時(shí)候震束,返回 true
怜庸。
#if targetEnvironment(simulator)
// code for the simulator here
#else
// code for real devices here
#endif
當(dāng)代碼用來(lái)處理類(lèi)似于從攝像頭讀取數(shù)據(jù)或者訪問(wèn)陀螺儀數(shù)據(jù)等模擬器不支持的功能的時(shí)候当犯,這個(gè)條件判斷很有用垢村。舉個(gè)例子,從攝像頭選擇照片嚎卫,如果是真機(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 部分場(chǎng)景更名 CompactMap
FlatMap
在 Swift 4.0 中很有用奠支,特別是在轉(zhuǎn)換集合中的對(duì)象馋辈,并且移除其中的 nil
對(duì)象的時(shí)候。Swift 提案 SE-0187 中有對(duì)這部分內(nèi)容更改的說(shuō)明倍谜,在 Swift 4.1 中 flatMap
為了語(yǔ)義更加清晰迈螟,已經(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 團(tuán)隊(duì)提升穩(wěn)定性的同時(shí),移除了大量代碼季春,自動(dòng)合成 Equatable
和 Hashable
的支持也使我們開(kāi)發(fā)更加便捷洗搂。其他一些在開(kāi)發(fā)或者在 Review
的提案,包括 SE-0192: Non-Exhaustive Enums, SE-0194: Derived Collection of Enum Cases,和 SE-0195: Dynamic Member Lookup – click here to learn more about new Swift features coming in 2018载弄。同這些新特性一樣重要的是耘拇,蘋(píng)果計(jì)劃在今年實(shí)現(xiàn) Swift 的 ABI
穩(wěn)定,期待??????宇攻。