一篇文章幫你徹底了解 Swift 3.1 的新內(nèi)容

本文翻譯自 What’s New in Swift 3.1?


好消息好消息:Xcode 8.3 和 Swift 3.1 正式版的發(fā)布包含了等待已久的 Swift 包管理器 功能,以及對(duì)語言本身的改進(jìn)。

如果你沒有一直密切關(guān)注 Swift Evolution Process 秒紧,那這篇文章就是為你準(zhǔn)備的巨双!

本文會(huì)強(qiáng)調(diào) Swift 3.1 中最重大的變動(dòng),這將對(duì)我們的代碼產(chǎn)生重大影響侠仇。來吧轻姿!:]

開始

Swift 3.1 和 Swift 3.0 是源碼兼容的,所以如果已經(jīng)使用 Edit\Convert\To Current Swift Syntax… 將項(xiàng)目遷移到了 Swift 3.0 的話逻炊,新功能將不會(huì)破壞我們的代碼互亮。不過,蘋果在 Xcode 8.3 中已經(jīng)拋棄了對(duì) Swift 2.3 的支持余素。所以如果還沒有從 Swift 2.3 遷移過來豹休,現(xiàn)在要抓緊做了!

下面會(huì)有類似 [SE-0001] 的標(biāo)簽鏈接桨吊。這些是 Swift Evolution 的提案號(hào)威根。我列出了每個(gè)提案的鏈接,以便查看某變動(dòng)完整的細(xì)節(jié)信息视乐。我建議在 playground 上試一下我們討論的功能洛搀,以便對(duì)于所有變動(dòng)都有更好的理解。

所以佑淀,啟動(dòng) Xcode留美,選擇 File\New\Playground…. 選擇 iOS 平臺(tái),隨便取個(gè)名字。一邊讀一邊在 playground 中嘗試每個(gè)功能独榴。

注意:如果需要簡單回顧一下 Swift 3.0 重點(diǎn)僧叉,看看這篇文章 What’s New in Swift 3

語言改進(jìn)

首先棺榔,看看這次發(fā)布中的語言改進(jìn)瓶堕,包括數(shù)字類型的可失敗初始化方法(failable initializers)、新的序列函數(shù)等等症歇。

可失敗數(shù)值轉(zhuǎn)換初始化方法

Swift 3.1 為所有數(shù)字類型 (Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double) 實(shí)現(xiàn)了可失敗初始化方法郎笆,要么完全成功、不損失精度忘晤,要么返回 nil [ SE-0080 ]宛蚓。

這個(gè)功能很實(shí)用,例如设塔,以安全凄吏、可還原(recoverable)的方式轉(zhuǎn)換來自外部的非確切類型數(shù)據(jù)。舉個(gè)實(shí)際的例子闰蛔,我們可能會(huì)這樣處理一個(gè)學(xué)生的 JSON 數(shù)組:

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)]

我們?cè)?Student 類的指定可失敗初始化方法中用可失敗構(gòu)造器將 grade 屬性從 Double 轉(zhuǎn)換為 Int痕钢,就像這樣:

let grade = Int(exactly: gradeDouble)

如果 gradeDouble 是小數(shù),例如 6.33序六,就會(huì)失敗任连。如果可以用 Int 來表示,例如 6.0例诀,就會(huì)成功随抠。

注意:還有一種可替代的設(shè)計(jì)是使用拋出錯(cuò)誤的初始化方法而不是可失敗的。社區(qū)選擇了可失敗的繁涂,因?yàn)樗霉八⒏瞎こ虒W(xué)設(shè)計(jì)。

新的序列函數(shù)

Swift 3.1 為標(biāo)準(zhǔn)庫的 Sequence 協(xié)議增加了兩個(gè)新函數(shù)扔罪,用于數(shù)據(jù)過濾:prefix(while:) 和 drop(while:) [ SE-0045 ]秉沼。

考慮斐波那契無限數(shù)列:

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 中,我們只需指定迭代次數(shù)(iteration count)即可遍歷斐波那契數(shù)列:

// 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:) 來獲取位于序列兩個(gè)給定值之間所有的元素步势,像這樣:

// 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 的最長子序列氧猬。從序列的開頭開始,并且在第一個(gè)從給定閉包中返回 false 的元素處停下坏瘩。

drop(while:) 做相反的操作:從第一個(gè)在閉包中返回 false 的元素開始盅抚,直到序列的結(jié)束,返回此子序列倔矾。

注意:這種情況下可以使用尾隨閉包(trailing closure):
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}

Concrete Constrained Extensions

Swift 3.1 允許我們擴(kuò)展具有 concrete type constraint 的泛型妄均。之前不能擴(kuò)展這樣的類型柱锹,因?yàn)榧s束必須是一個(gè)協(xié)議》岚看一個(gè)例子禁熏。

例如,Ruby On Rails 提供了一個(gè)非常實(shí)用的方法 isBlank 用于檢查用戶輸入邑彪。在 Swift 3.0 中我們會(huì)將其實(shí)現(xiàn)為 String 擴(kuò)展中的計(jì)算屬性

// 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 計(jì)算屬性瞧毙,在 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

我們創(chuàng)建了一個(gè) StringProvider 協(xié)議供 String 采用。當(dāng)拆包類型是 StringProvider 的時(shí)候使用它擴(kuò)展 Optional寄症,添加 isBlank 方法宙彪。

Swift 3.1 可以不用這樣的協(xié)議來擴(kuò)展 concrete type:

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

與之前相同的功能,但是代碼更少有巧!

泛型嵌套

Swift 3.1 讓我們可以混合使用泛型和類型嵌套释漆。練習(xí)一下,看看這個(gè)(不是很難的)例子篮迎。如果某個(gè) raywenderlich.com 的團(tuán)隊(duì)領(lǐng)導(dǎo)想要在博客上發(fā)一篇文章男图,他會(huì)找專門的開發(fā)者團(tuán)隊(duì)來處理這個(gè)問題,以保證文章的質(zhì)量:

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
  }
}

我們把內(nèi)部類 BlogPost 嵌套在對(duì)應(yīng)的外部類 Team 中甜橱,這兩個(gè)類都是泛型逊笆。目前團(tuán)隊(duì)在尋找已發(fā)布的教程和文章時(shí)需要這樣做:

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()))

但實(shí)際上可以簡化這里的代碼。如果嵌套的內(nèi)部類型用了外部的泛型渗鬼,它就默認(rèn)繼承了父類的類型览露。因此我們不需要聲明荧琼,只要這樣寫就可以了:

class Team<T> {
  // 本來的代碼
 
  class BlogPost {
    // 本來的代碼
  }  
 
  // 本來的代碼
  let blogPost: BlogPost
 
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    // 本來的代碼   
  }
}

注意:如果想學(xué)習(xí) Swift 中的泛型譬胎,讀一讀這篇最近更新的教程 getting started with Swift generics

Swift 版本可用性

我們可以使用 Swift ?版本的 #if swift(>= N) 靜態(tài)構(gòu)造器命锄,像這樣:

// 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 標(biāo)準(zhǔn)庫這樣的東西時(shí)堰乔,這種方法有一個(gè)很大的缺點(diǎn)。它需要為每個(gè)舊語言版本編譯標(biāo)準(zhǔn)庫脐恩。因?yàn)槿绻褂?Swift 3.0 的行為镐侯,則需要使用針對(duì)該版本編譯的標(biāo)準(zhǔn)庫。如果使用 3.1 版本的標(biāo)準(zhǔn)庫驶冒,就根本沒有正確的代碼苟翻。

所以,Swift 3.1 擴(kuò)展了 @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)
}

這個(gè)新功能與 intVersion 方法相同骗污。但是崇猫,它只允許像標(biāo)準(zhǔn)庫這樣的庫被編譯一次。編譯器隨后只要選擇與對(duì)應(yīng)版本兼容的功能即可需忿。

注意:如果想學(xué)習(xí) Swift 的 availability attributes诅炉,看看這篇教程 availability attributes in Swift蜡歹。

將非逃逸閉包轉(zhuǎn)換為逃逸閉包

Swift 3.0 把函數(shù)的閉包參數(shù)改為默認(rèn)非逃逸 [SE-0103]。但是涕烧,該提案當(dāng)時(shí)并沒有被完全實(shí)現(xiàn)月而。在 Swift 3.1 中,可以使用新的輔助函數(shù) withoutActuallyEscaping() 臨時(shí)轉(zhuǎn)換非逃逸閉包议纯。

為什么要這么做父款?雖然可能永遠(yuǎn)都不會(huì)用到,但還是考慮一下提案提到的這個(gè)例子瞻凤。

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
  }
}

此函數(shù)同時(shí)運(yùn)行兩個(gè)閉包铛漓,然后在兩個(gè)都完成之后返回。

  1. f 和 g 一開始是非逃逸的鲫构,并被轉(zhuǎn)換為 escapableF 和 escapableG浓恶。
  2. 調(diào)用 async(execute:) 需要逃逸閉包。很幸運(yùn)结笨,我們已經(jīng)搞定了包晰。
  3. 運(yùn)行 sync(flags: .barrier),以確保 async(execute:) 被全部完成了炕吸、之后不會(huì)再調(diào)用閉包伐憾。
  4. Scope 限制了 escapableF 和 escapableG 的使用。

如果不臨時(shí)轉(zhuǎn)換為逃逸閉包赫模,就會(huì)是一個(gè) bug树肃。標(biāo)準(zhǔn)庫未來應(yīng)該能夠檢測(cè)到這種錯(cuò)誤的調(diào)用。

Swift 包管理器的更新

啊啊啊啊瀑罗,期待已久的 Swift 包管理器終于更新了胸嘴!

Editable Packages

Swift 3.1 在 Swift 包管理器 中新增了 Editable Packages 概念 [ SE-0082 ]。

swift package edit 命令可以將現(xiàn)有包轉(zhuǎn)換為可編輯的斩祭×酉瘢可編輯的包將會(huì)替換 dependency graph 中的規(guī)范包。使用 —end-edit 命令將包管理器還原回規(guī)范解析的包摧玫。

Version Pinning

Swift 3.1 在特定版本的 Swift 包管理器 中新增了 version pinning 概念 [ SE-0145 ]耳奕。 pin 命令會(huì)像這樣固定一個(gè)或多個(gè)依賴:

$ swift package pin --all      // pin 所有依賴
$ swift package pin Foo        // 把 Foo pin 在當(dāng)前解析版本 
$ swift package pin Foo --version 1.2.3  // 把 Foo pin 在 1.2.3

使用 unpin 命令恢復(fù)到以前的包版本:

$ swift package unpin —all
$ swift package unpin Foo

包管理器在 Package.pins 中存儲(chǔ)每個(gè)包的活躍版本 pin 信息。如果文件不存在诬像,包管理器則會(huì)按照包 manifest 中指定的要求自動(dòng)創(chuàng)建該文件屋群,這是 automatic pinning 過程的一部分。

其它

swift package reset 命令將包重置為干凈狀態(tài)坏挠,不會(huì)檢出當(dāng)前的依賴關(guān)系或 build artifact芍躏。

還有,使用 swift test --parallel 命令并行執(zhí)行測(cè)試癞揉。

雜項(xiàng)

Swift 3.1 還有一些并不屬于以上某個(gè)分類的改動(dòng):

多重返回函數(shù)

返回兩次的 C 函數(shù)(如 vfork 和 setjmp )將無法繼續(xù)使用纸肉。它們以可笑的方式改變了程序的控制流程溺欧。所以 Swift 社區(qū)已經(jīng)決定禁止使用它們,現(xiàn)在直接會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤柏肪。

禁用 Auto-Linking

Swift 包管理器 禁用了 C 語言 target module mapsauto-linking 功能:

// Swift 3.0
module MyCLib {
    header “foo.h"
    link “MyCLib"
    export *
}
 
// Swift 3.1
module MyCLib {
    header “foo.h”
    export *
}

下一步姐刁?

Swift 3.1 優(yōu)化了一些 Swift 3.0 的功能,為將在今年推出的 Swift 4.0 中捆綁的重大改動(dòng)做準(zhǔn)備烦味。包括對(duì)泛型的巨大改進(jìn)聂使,正則表達(dá)式,更符合工程學(xué)的字符串設(shè)計(jì)等等谬俄。

如果你喜歡挑戰(zhàn)柏靶,可以看看 Swift standard library diffs ,或者查看官方的 Swift CHANGELOG 溃论,可以在其中閱讀有關(guān)所有改動(dòng)的更多信息屎蜓。或者可以用它來了解 Swift 4.0 中的內(nèi)容钥勋!

如果好奇 Swift 4 及更高版本中的改動(dòng)炬转,可以去 Swift Evolution proposals ,可以看到現(xiàn)在提案的內(nèi)容算灸。如果真的很熱愛 Swift扼劈,為什么不就目前的提案提出意見,甚至自己提出提案呢菲驴?:]

所以你現(xiàn)在喜不喜歡 Swift 3.1荐吵?歡迎在下方評(píng)論!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赊瞬,一起剝皮案震驚了整個(gè)濱河市先煎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌森逮,老刑警劉巖榨婆,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磁携,死亡現(xiàn)場(chǎng)離奇詭異褒侧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谊迄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門闷供,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人统诺,你說我怎么就攤上這事歪脏。” “怎么了粮呢?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵婿失,是天一觀的道長钞艇。 經(jīng)常有香客問我,道長豪硅,這世上最難降的妖魔是什么哩照? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮懒浮,結(jié)果婚禮上飘弧,老公的妹妹穿的比我還像新娘。我一直安慰自己砚著,他們只是感情好次伶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稽穆,像睡著了一般冠王。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舌镶,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天版确,我揣著相機(jī)與錄音,去河邊找鬼乎折。 笑死绒疗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的骂澄。 我是一名探鬼主播吓蘑,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼坟冲!你這毒婦竟也來了磨镶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤健提,失蹤者是張志新(化名)和其女友劉穎琳猫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體私痹,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脐嫂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了紊遵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片账千。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖暗膜,靈堂內(nèi)的尸體忽然破棺而出匀奏,到底是詐尸還是另有隱情,我是刑警寧澤学搜,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布娃善,位于F島的核電站论衍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏聚磺。R本人自食惡果不足惜饲齐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咧最。 院中可真熱鬧捂人,春花似錦、人聲如沸矢沿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捣鲸。三九已至瑟匆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栽惶,已是汗流浹背愁溜。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留外厂,地道東北人冕象。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像汁蝶,于是被迫代替她去往敵國和親渐扮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • 本文來自簡書掖棉,原文地址:http://www.reibang.com/p/bb0bd1881a35 好消息好消息:...
    XLsn0w閱讀 274評(píng)論 0 0
  • 開始Swift 3.1 和 Swift 3.0 是源碼兼容的墓律,所以如果已經(jīng)使用 Edit\Convert\To C...
    DY_108閱讀 273評(píng)論 0 0
  • 本文翻譯自raywenderlich.com中的文章《What’s New in Swift 4?》,由于本人...
    周杰木有倫閱讀 1,091評(píng)論 0 4
  • 說起夫妻相處幔亥,聽過很多耻讽,也看過很多,覺得都有道理帕棉≌敕剩可是遇到實(shí)際問題,落實(shí)到實(shí)際生活中笤昨,卻不怎么容易祖驱。 ...
    藍(lán)天碧海123456閱讀 180評(píng)論 1 1
  • 從上班開始,便和辦公桌結(jié)下了不解之緣瞒窒。 在辦公室里,有我的一張辦公桌乡洼。棕色的崇裁,長方形匕坯。和六個(gè)同事或者十二個(gè)同事處于...
    一品歲月閱讀 282評(píng)論 0 2