在剛完成的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
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)
}
函式參數中移除 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 開發者容易混淆 var
和 inout
,故此不再允許把函式參數設置成變數。 而最新版本的 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
程式碼頗長,讓我們將它分成幾步來進行說明:
- 導入 UIKit 和 XCPlayground 框架 - 我們需要利用這些去建立按鈕,並把它顯示到 playground 的助理編輯器。
- 為 tap 方法定義,當用戶點擊按鈕便會觸發這個方法,並把一個回應物件回傳給按鈕目標 - 這需要堅於一個 NSObject,因為選擇器只對 Objective-C 方法有效。
-
宣告按鈕並設置其屬性。
-
宣告視圖及其相應的資訊框,在視圖添加一個按鈕,然後在 playground 的助理上顯示出來。
看看以黃色突出顯示的程式碼。按鈕選擇器是一個字串。若果你錯誤輸入的話,當程式執行時,程式碼便會出現問題,因為找不到相應的方法而導致程式錯誤彈出。
要解決這個編譯時潛在問題, Swift 3 使用了 #selector()
關鍵字取代了選擇器。這樣編譯器能夠及早偵查到相關問題之所在。
button.addTarget(responder, action: #selector(Responder.tap), for: .touchUpInside)
以上就是 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))
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)
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")
}
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
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 將會給予你警告。例如:
在上述的程式碼,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 中能夠順利執行。
如果有任何疑問或困難,歡迎留言查詢。