What's new in Swift 3

原文:What’s New in Swift 3
作者:COSMIN PUPĂZĂ
译者:kmyhy

在 WWDC 大會上,蘋果在 Xcode 8 beta 中集成了 Swift 3,最後的版本則需要到年末的時候才會放出。這是 Swift 開源以後第一個版本,它將同時支持 Mac OS X 和 Linux。如果你關注過去年 11 月份開始的 Swift Evolution 專案,它甚至已經可以在 IBM sandbox 上運行了,這門語言真的有了天翻地覆的變化。如果你用 Xcode 8 編譯你的專案,你會發現根本無法編譯了。

Swift 3 的改進主要集中在兩個方面:

  • 刪除在 Swift 2.2 中聲明的“已拋棄”特性
  • 讓語言更加“先進”

讓我們先從简单的开始,即 Swift 3 中已刪除的語法特性。這些特性是你在 Xcode 7.3 的中曾經看到過的一些警告。

++ 和 – 運算子

自增、自減運算是從 C 語言中繼承來的,它們的功能很簡單:在某個變量的基礎上減去或加上 1。

var i = 0
i++
++i
i--
--i

但是,当要选择用这些運算中的哪一個時,往往讓人不知所措。自增和自減運算子都會有兩種使用的方式:前置或後置 ── 它們的實現完全隱藏在底層,返回值對你來說可能有用,也可能無用,幸好我們還有運算子重載。

對於初學者來說,它們太難控制了,因此現在將它們移除了 ── 現在它們完全被 += 和 ﹣= 所替代了:

var i = 0
i += 1
i -= 1

雖然復合賦值運算要簡短一些,但只要你願意,你也可以用 + 和 ﹣ 運算来達到同樣的目的 :

i = i + 1
i = i - 1

[ecko_alert color=”gray”]編後語: 如果你想了解這项改進的最初提議,你可以閱讀Chris Lattner 的關於移除 ++/– 運算子的提議。[/ecko_alert]

C 語言風格的 for 循環成為歷史

對於自增、自減運算用得最多的地方就是 C 語言的經典循環了。當自增、自減運算子被移除后,同時也意味著 C 經典循環的歷史使命被終結了,因為經典循環能做的,用 for-in 循環結合區間的使用同樣能夠做到。

如果你有一定的編程經驗,那麼打印從 1 到 10 的數字你可以用 for 循環來實現:

for (i = 1; i <= 10; i++) {
  print(i)
}

在 Swift 3 中,不再允許這樣做了。下面是 Swift 3 的同一功能的實現 ── 注意,封閉區間運算子 … 的使用:

for i in 1...10 {
  print(i)
}

此外,你也可以使用 for each 循環和閉包、快捷參數來實現同樣的目的 ── 關於循環的更多討論,請參考這裡。

(1...10).forEach {
  print($0)
}

[ecko_alert color=”gray”]編後語: 如果你想了解這項改進的最初提議,請閱讀 Erica Sadun 的關於 C 語言風格的 for 循環的提議 。[/ecko_alert]

函數參數中的 var 被移除

函數參數一般是當做常量來用的,因為在方法中你通常不會修改它們的值。但是某些情況下,你需要將它們聲明為變量。在 Swift 2 中,你可以用 var 來修飾一個函數參數。一旦參數被 var 所修飾,它會創建一份局部變量的拷貝,因此你可以在函數中修改它的值。

例如,下面的函數判斷兩個數字中的最大公約數 ── 如果你忘記了你的高中數學,請你先看這裡:

func gcd(var a: Int, var b: Int) -> Int {

  if (a == b) {
    return a
  }

  repeat {
    if (a > b) {
      a = a - b
    } else {
      b = b - a
    }
  } while (a != b)

  return a
}

算法很簡單:如果兩個數字相等,隨便返回其中一個。否則,對二者進行比較,用大的一個減去小的一個并將結果賦給大的一個,直到二者完全相等,最終返回二者之一。如你所見,a 和 b 都用 var 修飾了,因此在函數中我們可以任意改變二者的值。

Swift 3 不再允許這樣做,因為這樣會讓開發者將 var 和 inout 混淆起來。因此新版本中直接不允許在函數參數中使用 var。

因此,gcd 函數需要在 Swift 3 中用其它方式實現。你需要將參數值保存到一個局部變量中:

func gcd(a: Int, b: Int) -> Int {

  if (a == b) {
    return a
  }

  var c = a
  var d = b

  repeat {
    if (c > d) {
      c = c - d
    } else {
      d = d - c
    }
  } while (c != d)

  return c
}

如果你想了解這項改進的最初提議,請閱讀原始提議。

對函數的參數名一視同仁

函數的參數列表,底層上實際是以元組(tuple)表示的,因此你可以以元組的方式調用函數,就好像調用一個和函數原型相同的元組。以 gcd() 函數為例,你可以這樣調用它:

gcd(8, b: 12)

也可以這樣調用它:

let number = (8, b: 12)
gcd(number)

如你所見,在 Swift 2 中第一個參數的 Label(又叫外部參數名)不是必須的。但是,第二個參數的參數名(以及剩餘的其它參數名)則是必須的。

這種語法也會讓新手茫然,因此這次對參數名的使用進行了規範化。在 Swift 3 中,你必須這樣調用這個函數:

gcd(a: 8, b: 12)

必須顯式地指定第一個參數名。否則,Xcode 8 會提示錯誤。

對此你的第一反應可能是“上帝!這得給我的程式碼帶來多大的修改量!”。你說對了,這個修改量還真不小。因此蘋果提供了一個調用函數時忽略第一個參數名的方法。你可以在第一個參數前面加一個下劃線:

func gcd(_ a: Int, b: Int) -> Int {

...

}

這樣,你可以不用修改原來的函數調用方式 ── 即不用書寫第一個參數名。這會簡少將程式碼從 Swift 2 升級到 Swift 3 的工作量。

[ecko_alert color=”gray”]編後語: 要了解這項改進的最初提議,請閱讀這個提議。[/ecko_alert]

不能用字符串表示 Selector

讓我們創建一顆按鈕,讓它被觸摸時執行某些動作 ── 假設只能用 playground 而不能用 Interface Builder 實現的話,這段程式碼應當編寫成:

// 1
import UIKit
import XCPlayground

// 2
class Responder: NSObject {

  func tap() {
    print("Button pressed")
  }
}
let responder = Responder()

// 3
let button = UIButton(type: .System)
button.setTitle("Button", forState: .Normal)
button.addTarget(responder, action: "tap", forControlEvents: .TouchUpInside)
button.sizeToFit()
button.center = CGPoint(x: 50, y: 25)

// 4
let frame = CGRect(x: 0, y: 0, width: 100, height: 50)
let view = UIView(frame: frame)
view.addSubview(button)
XCPlaygroundPage.currentPage.liveView = view

程式碼有點多,讓我們將它分成幾步來進行說明:

  1. 導入 UIKit 和 XCPlayground 框架 ── 這樣我們才能創建按鈕并將它顯示到 playground 的助手編輯器中。

    注意: 通過 View -> Assistant Editor -> Show Assistant Editor 菜單,你可以打開助手編輯器并與按鈕進行交互。

  2. 定義一個 tap 方法,這個方法會在用戶觸摸按鈕時觸發,并將一個 responder 對象傳遞給按鈕的 target ── responder 必須繼承自 NSObject,因為 selector 對象只對 Objective-C 方法有效。

  3. 創建一個按鈕并設置其屬性。

  4. 創建一個視圖,初始化時指定其 frame,然後將按鈕加到 view 上,然後在 playground 的助手編輯器上顯示。

注意突出顯示的程式碼。按鈕的 selector 用一個字符串指定。如果你將這個寫錯了,程式碼仍然能夠編譯,但在運行時會因為找不到相應的方法而崩潰。

為了在編譯時解決這種潛伏的問題,Swift 3 使用 #selector() 關鍵字來表示 selector。這樣,如果你寫錯了方法名,編譯器就會提前檢查出問題所在。

button.addTarget(responder, action: #selector(Responder.tap), for: .touchUpInside)

[ecko_alert color=”gray”]編後語: 要了解這項改進的最初提議,請閱讀 Doug Gregor 的提議。[/ecko_alert]

上面就是 Swift 中已刪除的語法特性。現在讓我們看一下讓 Swift 更加具有“先進性”的改進有哪些。

Key-paths 不再使用字符串

這個特性跟前一個有點類似,但它被用於 kvc(鍵值編碼)和 kvo(鍵值觀察)。

class Person: NSObject {
  var name: String = ""

  init(name: String) {
    self.name = name
  }
}
let me = Person(name: "Cosmin")
me.valueForKeyPath("name")

你創建了一個 Person 類,它是鍵值編碼兼容的,在指定的初始化方法中傳入了我的名字,然後通過 key-path 來讀取我的名字。同樣,如果你把 key-path 弄錯了,app 會崩潰,我會很生氣!:(

幸運的是,Swift 3 解決了這個問題。key-path 字符串現在需要用 #keyPath() 表達式來替換:

class Person: NSObject {
  var name: String = ""

  init(name: String) {
    self.name = name
  }
}
let me = Person(name: "Cosmin")
me.value(forKeyPath: #keyPath(Person.name))

[ecko_alert color=”gray”]編後語: 關於這個改進的最初提議,你可以查看 David Hart 的提議。[/ecko_alert]

Foundation 類型名不再需要 NS 前綴

NS 前綴終於從 Foundation 類型名中移除了 ── 如果你想知道這項改進的最初提議,請看這裡。典型的例子如 JSON 解析:

let file = NSBundle.mainBundle().pathForResource("tutorials", ofType: "json")
let url = NSURL(fileURLWithPath: file!)
let data = NSData(contentsOfURL: url)
let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)

在將 JSON 數據從文件中讀出的過程中,我們使用了幾個 Foundation 類:
NSBundle -> NSURL -> NSData -> NSJSONSerialization

在 Swift 3 中,NS 前綴被移除了,因此上面的解析過程就變成了:
Bundle -> URL -> Data -> JSONSerialization:

let file = Bundle.main().pathForResource("tutorials", ofType: "json")
let url = URL(fileURLWithPath: file!)
let data = try! Data(contentsOf: url)
let json = try! JSONSerialization.jsonObject(with: data)
print(json)

[ecko_alert color=”gray”]編後語: 要想知道這項改進的最初提議,請查看 Tony Parker 和 Philippe Hausler 的 這個提議 [/ecko_alert]

M_PI 和 .pi

讓我們用圓半徑算出圓的周長和面積:

let r =  3.0
let circumference = 2 * M_PI * r
let area = M_PI * r * r

在老的 Swift 版本中,M_PI 代表了圓周率。Swift 3 中,則將 pi 放到了 Float, Double 和 CGFloat 類型的內部:

Float.pi
Double.pi
CGFloat.pi

因此前面的(計算圓周長和面積)的程式碼,用 Swift 3 則可以編寫為:

let r = 3.0
let circumference = 2 * Double.pi * r
let area = Double.pi * r * r

由於類型推斷的存在,我們甚至可以忽略類型名,因此代碼可以簡化為:

let r = 3.0
let circumference = 2 * .pi * r
let area = .pi * r * r

GCD

GCD(Grand Central Dispath) 通常被用於執行網絡操作而不阻塞主線程中的用戶界面。它是用 C 編寫的,因此它的 API 對於初學者來說難於理解,尤其是想在異步隊列中做某些工作的時候:

let queue = dispatch_queue_create("Swift 2.2", nil)
dispatch_async(queue) {
  print("Swift 2.2 queue")
}

Swift 3 刪除了這些公式化和繁瑣的語法,改用面向對象的方法來實現:

let queue = DispatchQueue(label: "Swift 3")
queue.async {
  print("Swift 3 queue")
}

[ecko_alert color=”gray”]編後語: 要了解這項改進的最初提議,請閱讀 Matt Wright 的這個提議 [/ecko_alert]

Core Graphics 變得更加“Swift 化”

Core Graphics 是一個強大的圖形框架,但使用了跟 GCD 一樣 C 風格的 API:

let frame = CGRect(x: 0, y: 0, width: 100, height: 50)

class View: UIView {

  override func drawRect(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    let blue = UIColor.blueColor().CGColor
    CGContextSetFillColorWithColor(context, blue)
    let red = UIColor.redColor().CGColor
    CGContextSetStrokeColorWithColor(context, red)
    CGContextSetLineWidth(context, 10)
    CGContextAddRect(context, frame)
    CGContextDrawPath(context, .FillStroke)
  }
}
let aView = View(frame: frame)

你需要先指定 view 的 frame,繼承 UIView 類,重寫 drawRect 方法以進行定制的繪圖操作,使 view 呈現不一樣的畫面。

Swift 3 中,使用了一種全新的方法來使用 Core Graphic ── 先獲取前繪圖上下文,將它進行解包,解包到一個 context 對象,然後再通過這個 context 來執行所有的繪圖操作:

let frame = CGRect(x: 0, y: 0, width: 100, height: 50)

class View: UIView {

  override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else {
      return
    }

    let blue = UIColor.blue().cgColor
    context.setFillColor(blue)
    let red = UIColor.red().cgColor
    context.setStrokeColor(red)
    context.setLineWidth(10)
    context.addRect(frame)
    context.drawPath(using: .fillStroke)
  }
}
let aView = View(frame: frame)

注意: 在 view 調用它的 drawRect 方法之前,圖形上下文為空,因此你需要對其用 guard 語句進行解包操作 ── 更多內容請閱讀這裡。

動詞/名詞命名規範

接下來該講一點語法了!:) Swift 3 將方法分成兩類:有返回值的 ── 使用名詞命名,以及執行某種操作的 ── 使用動詞命名。

下面打印從 10 到 1 的數字:

for i in (1...10).reverse() {
  print(i)
}

reverse() 方法用於將區間反序。在 Swift 3 看來,這個方法應該用名詞,因為它將原來的區間反向排序后返回。因此,它在這個方法後面加了一個 ed 後綴:

for i in (1...10).reversed() {
  print(i)
}

元組最常用的用法之一,就是打印一個數組的內容:

var array = [1, 5, 3, 2, 4]
for (index, value) in array.enumerate() {
  print("\(index + 1) \(value)")
}

在 Swift 3 中,這個方法應該用名詞,因為它也返回了一個由當前數組元素的索引和值共同組成的元組。因此它在這個方法後面也加了一個 ed 後綴:

var array = [1, 5, 3, 2, 4]
for (index, value) in array.enumerated() {
  print("\(index + 1) \(value)")
}

另一個例子是數組的排序。下面我們將一個數組按照升序進行排序:

var array = [1, 5, 3, 2, 4]
let sortedArray = array.sort()
print(sortedArray)

在 Swift 3 中,這個方法應該使用名詞,因為它返回了經過排序的數組。因此 sort 方法現在被命名為 sorted 方法:

var array = [1, 5, 3, 2, 4]
let sortedArray = array.sorted()
print(sortedArray)

讓我們直接在數組上進行操作,去掉中間變量。在 Swift 2 中,你會用這樣的程式碼:

var array = [1, 5, 3, 2, 4]
array.sortInPlace()
print(array)

你通過 sortInPlace() 方法對一個可變數組排序。Swift 3 中,這個方法名應該用動詞,因為它直接進行實際上的排序操作,而不返回任何東西。只需要用最簡單的單詞就可以描述這種動作。因此 sortInPlace() 應該被 sort() 代替:

var array = [1, 5, 3, 2, 4]
array.sort()
print(array)

[ecko_alert color=”gray”]編後語: 關於這項命名規範的最初提議,請閱讀 API 設計指南。[/ecko_alert]

API 更加“swift 化”

Swift 3 使用了一個簡單的哲學來規範其 API ── 刪除無用的單詞,如果某個單詞是多餘的或者是能夠通過上下文推斷出來的,則刪除它:

  • XCPlaygroundPage.currentPage 改為 PlaygroundPage.current
  • button.setTitle(forState) 改為 button.setTitle(for)
  • button.addTarget(action, forControlEvents) 改為 button.addTarget(action, for)
  • NSBundle.mainBundle() 改為 Bundle.main()
  • NSData(contentsOfURL) 改為 URL(contentsOf)
  • NSJSONSerialization.JSONObjectWithData() 改為 JSONSerialization.jsonObject(with)
  • UIColor.blueColor() 改為 UIColor.blue()
  • UIColor.redColor() 改為 UIColor.red()

枚舉值

Swift 3 將枚舉值看成屬性,因此使用“小駝峰法”而不是“大駝峰法”進行命名:

  • .System 改為 .system
  • .TouchUpInside 改為 .touchUpInside
  • .FillStroke 改為 .fillStroke
  • .CGColor 改為 .cgColor

@discardableResult

在 Swift 3 中,如果你不使用方法或函數的返回值,Xcode 會顯示警告,例如:

What's new in Swift 3_第1张图片

在上面的程式碼中,printMessage 方法返回一個字符串。但是,這個返回值在我們調用方法的時候沒有用到,這可能引發潛在的問題,因此 Swift 3 編譯器會發出警告。

但是在某些情況下,我們實在是沒有必要保留返回值,因此你可以用一個 @discardableResult 表明方法不需要這種警告:

override func viewDidLoad() {
    super.viewDidLoad()

    printMessage(message: "Hello Swift 3!")
}

@discardableResult
func printMessage(message: String) -> String {
    let outputMessage = "Output : \(message)"
    print(outputMessage)

    return outputMessage
}

結束

關於 Swift 3 就介紹到這裡。新的 Swift 版本的發佈,讓這門語言越來越好用啦。在它包含了大量的功能改進的同時,也會對你現在的代碼產生非常大的影響。我希望這篇教程能讓你更好地理解這些改進,同時節省你升級 Swift 專案的時間成本。

本教程中的所有程式碼都可以在 這裡 下載。我已經在 Xcode 8 beta 中通過了測試。請確保你在 Xcode 8 中運行這個專案。

如果你有任何問題和建議,請告訴我。祝你 coding 快樂! :)

譯者簡介

楊宏焱,男,中國大陸籍人士,CSDN 博客專家(個人博客 http://blog.csdn.net/kmyhy)。2009 年開始學習蘋果 iOS 開發,精通 O-C/Swift 和 Cocoa Touch 框架,開發有多個商店應用和企業 App。熱愛寫作,著有和翻譯有多本技術專著,包括:《企業級 iOS 應用實戰》、《iPhone & iPad 企業移動應用開發秘笈》、《iOS8 Swift 編程指南》,《寫給大忙人看的 Swift》(合作翻譯)、《iOS Swift game Development cookbook》等。

你可能感兴趣的:(iPhone开发,swift,wwdc)