Swift 3:你要知道的新特色和改動


在剛完成的WWDC大會上,蘋果發佈了新一代 Swift 3 和 Xcode 8 beta,並預計在本年第四季正式推出。自2015年12月,Swift語言正式開放源代碼,Swift 3 當然也不例外,同時支援 Mac OS X 及Linux 系統。如果你自上年十二月開始追踪 Swift Evolution,或已經曾在 IBM sandbox 試用過的話,或許已經心中有數 Swift 3 有著很大的改動。若果直接在 Xcode 8 上編譯現行的專案,幾乎可以肯定的說不會成功。

Swift 3 的更新主要分為兩個類別:

  • 刪除在 Swift 2.2 中部份過時的功能
  • 優化語言

我們先來看看被移除的部分,這些功能在 Xcode 7.3 時仍然可以使用,只是每當使用時會顯示黃色警告字句。在Swift 3,這些警告字句會變成編譯錯誤。

++ 和 – – 運算子

首先,和大家聊一聊 ++-- 運算子的改動。遞增和遞減運算子從C語言中繼承至 Swift,功能上簡單直接 – 為某個變數增加或減去 1。

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

然而,這些運算子的使用方法都略有不同,很容易讓人產生混淆。遞增和遞減運算子分別有兩種使用方法:前置或後置 -它們看來相似,但實際運作和結果都大有不同。

對於初學者而言這些都太複雜難懂了,所以在Swift 3都統統移除 – 由增加 (+=) 和 減去 (-=) 取而代之:

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

當然,你也可以使用增加 (+) 和減去 (-) 運算子,雖然複合指派運算子的做法會更簡短:

i = i + 1
i = i - 1
伸延閱讀: 如果你想了解更多關於這個改動背後的動機,請參考 Chris Lattner 移除 ++/– 運算子的建議

C 語言風格的 for 迴圈語法成為歷史

遞增和遞減運算子經常出現在C語言的迴環語法,當遞增和遞減運算子被移除,意味著唇齒相依的 for 迴環也走到末路,往後將會以更簡潔的 for-in 控制流程敍述實現相樣的功能。

假設你對編程有基本經驗,大概也會使用 for 迴環來列出1-10:

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

由 Swift 3 開始,就不再允許這種做法。優化後的程式碼將會如下 - 注意這是封閉範圍運算子 (…) 的使用:

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

另外,你也可以使用 for-each 迴環加以閉包和縮寫參數 - 關於迴環的更多資料,請參閱這裡

(1...10).forEach {
  print($0)
}
伸延閱讀: 如果你想了解更多關於這個改動背後的動機,請參考 Erica Sadun 關於棄用 C 語言風格的 for 循環的提議

函式參數中移除 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 都是變數,因此我們都可以更改它們的值。

Swift 3 為免 Swift 開發者容易混淆 varinout,故此不再允許把函式參數設置成變數。 而最新版本的 Swift 裡也把 var 從函式參數中刪除。

因此,在 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
}
伸延閱讀: 如果你想了解更多關於這個改動背後的動機,請參考原本建議

函式參數的一致標記特性

函式參數列實際上由元組表示,所以我們可以使用元組呼叫函數,就好像以元組結構配對函數原型。以 gcd() 功能為例。你可以這樣呼叫它:

gcd(8, b: 12)

也可以這樣調用它:

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

如你所見,在 Swift 2 中你不需要指定第一個參數標記,但需要為第二個(或其餘的)參數指定標記。

對菜鳥來說這個語法應該挺複雜,因此優化後對參數標記的使用進行了規範化。在 Swift 3, 你可以這樣呼叫函數:

gcd(a: 8, b: 12)

你必須明確指定第一個參數的標記。否則,Xcode 8 會提示出現錯誤。

這個改動意味著有大量的程式碼需要修改。沒錯,確實有很多修改的地方。因為蘋果提供了一個呼叫函數時忽略第一個參數標記的方法。這就是把下劃線添加到第一個參數之前:

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

...

}

通過這個方法,你可以沿用舊的方法調用函數 - 不用指定第一個標記。這將使你較輕易地把程式碼由 Swift 2 轉移至 Swift 3。

伸延閱讀: 如果你想了解更多關於這個改動背後的動機,請參考這個提議

不能再用字串來表示選擇器 (Selectors)

當我們要建立一個按鈕並讓它被觸及時執行某些動作 - 假設只能使用 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 的助理編輯器。
注意: 你可以從這個途徑去把Xcode的助理編輯器打開:View > Assistant Editor > Show Assistant Editor。
  1. 為 tap 方法定義,當用戶點擊按鈕便會觸發這個方法,並把一個回應物件回傳給按鈕目標 - 這需要堅於一個 NSObject,因為選擇器只對 Objective-C 方法有效。

  2. 宣告按鈕並設置其屬性。

  3. 宣告視圖及其相應的資訊框,在視圖添加一個按鈕,然後在 playground 的助理上顯示出來。

看看以黃色突出顯示的程式碼。按鈕選擇器是一個字串。若果你錯誤輸入的話,當程式執行時,程式碼便會出現問題,因為找不到相應的方法而導致程式錯誤彈出。

要解決這個編譯時潛在問題, Swift 3 使用了 #selector() 關鍵字取代了選擇器。這樣編譯器能夠及早偵查到相關問題之所在。

button.addTarget(responder, action: #selector(Responder.tap), for: .touchUpInside)
伸延閱讀: 如果你想了解更多關於這個改動背後的動機,請參考 Doug Gregor 的提議

以上就是 Swift 3 已移除的語法特性。現在讓我們看一下讓 Swift 3 優化後的亮點。

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 類別,在類別指定初始化中建立名字,使用通過鍵值來讀取名字。同樣,若果在這部份輸入錯誤,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))
伸延閱讀: 如果你想了解更多關於這個改動背後的動機,請參考 David Hart 的提議

Foundation 型態不再需要 NS 字首

NS 字首從 Foundation 型態中移除了 - 若果想重溫 NS 是什麼,可以查看這裡。一個 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)

你可以使用 Foundation 類別去連接檔案,並以這個方法提取 JSON 數據: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)
伸延閱讀: 如果你想了解更多關於這個改動背後的動機,請參考 Tony Parker 和 Philippe Hausler 的 這個提議

M_PI vs .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

Grand Central Dispatch

Grand Central Dispatch (GCD) 常用於網絡操作但不阻礙主執行緒的使用者介面。它是由 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")
}
伸延閱讀: 如果你想了解更多,請參考 Matt Wright 的這個提議

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)

你需要建立一個視圖資訊框,伸延 UIView 類別,覆寫 drawRect() 方法以進行自定繪圖,並使視圖展示新的內容。

來到 Swift 3 將會用上煥然一新的方法 - 首先打開現時圖像內容,然後執行所有繪圖有關操作:

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)

注意:在視圖呼叫 drawRect() 方法之前,內容是空白的,所以你需要使用 guard 敍述去打開它 - 按此了解更多。

動詞 VS 名詞命名規約

Swift 3 將方法分成兩個組別:有返回值的 - 把它們當作名詞;以及執行某種動作的方法 - 把它們當作動詞。

以下是以現有 Swift 程式碼把 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 中就需要為 enumerate() 加上 “ed”:

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

另一個例子是陣列排序。下面我們將以現時的 Swift 程式碼為陣列按升序排序:

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)

把陣列直接排序,不使用中間常數。現時我們寫的程式碼像這樣:

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)
伸延閱讀: 如果你想了解更多,請參考 API 設計指南

優化 API

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

列舉 Cases

Swift 3 例舉 cases 如像屬性,因此使用 「lowerCamelCase」 而不是「upperCamelCase」 進行命名:

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

@discardableResult

在 Swift 3 中,若果你不使用一個函數或方法的返回值,Xcode 將會給予你警告。例如:

discardable-result-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 程式碼帶來重大的影響。我希望這篇教程能夠讓你更清楚這些改動,也期望這些能夠節省你遷移 Swift 專案的時間。

本教程中的所有程式碼都可以在這裡下載。這些程式碼我都已經親身試驗過,確保在 Xcode 8 beta 中能夠順利執行。

如果有任何疑問或困難,歡迎留言查詢。

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

原文What's New in Swift 3


Cosmic Pupăză 在部落格 cosminpupaza.wordpress.com 分享有關 Swift 和 iOS 開發文章,以及參與了 raywenderlich.com 和 appcoda.com 兩個平台的 Swift 教學團隊。平日喜歡玩結他和研究二戰歷史。可以透過電郵:[email protected],在Facebook,Twitter 及 Google+ 找到 Cosmic。

blog comments powered by Disqus
Shares
Share This