做者:COSMIN PUPĂZĂ,原文連接,原文日期:2016/06/29
譯者:saitjr;校對:Cee;定稿:CMBhtml
Apple 在 WWDC 上已將 Swift 3 整合進了 Xcode 8 beta 中,並會在今年晚些時候發佈 Swift 3 的正式版。這是 Swift 在開源和支持 Mac OS X 與 Linux 以後的首個版本。若是你在去年 11 月關注了 Swift 進化史 和已經啓動的 IBM 沙盒 項目,那你應該知道 Swift 確實改動不少。甚至能夠肯定你在 Xcode 8 上根本沒法編譯既有項目。ios
Swift 3 的改動歸結下來主要有兩點:git
移除了在 Swift 2.2 就已經棄用的特性github
語言現代化問題算法
讓咱們從移除特性講起,畢竟這點能容易理解,並且在 Xcode 7.3 的時候咱們遇到了相關警告。編程
++
與 --
操做符自增自減是來源於 C 的操做符,做用是對變量直接進行 +1
或 -1
的操做:json
var i = 0 i++ ++i i-- --i
然而,在咱們要選擇使用哪種操做符進行運算的時候,事情就變得複雜起來。不管是自增仍是自減,都對應着兩種寫法:寫在在變量以前,仍是在變量以後。它們的底層實現其實都是有返回值的函數,是否使用返回值取決於對運算符的重載。swift
這可能會嚇跑初學者,因此蘋果移除了該特性——取而代之的是複合加法運算(+=
)與減法運算(-=
):api
var i = 0 i += 1 i -= 1
固然,你也可使用普通的加法運算(+
)與減法運算(-
),雖然複合式運算符寫起來要短一點:數組
i = i + 1 i = i - 1
延伸閱讀:若是你想要了解更多該變動背後的故事,請閱讀 Chris Lattner 對移除
++
與--
的見解。
其實自增自減運算符用得最多的地方,仍是在 for 循環部分。移除該運算符意味着 for 循環的特性也隨之遠去了,由於在 for-in 的世界中,循環控制語句與範圍限制用不上該操做符。
若是你有必定編程背景,那麼輸出 1 到 100 的數,你可能會這樣寫:
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 風格循環的見解。
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 不在容許開發者這樣來將參數標記爲變量了,由於開發者可能會在 var
和 inout
糾結不已。因此最新的 Swift 版本中,就乾脆移除了函數參數標記 var
的特性。
如此,想要用 Swift 3 來寫上面的 gcd
函數,就要另闢蹊徑了。你須要在函數內部建立臨時變量來存儲參數:
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 }
延伸閱讀:若是你想要了解更多該變動背後的故事,請閱讀決定移除
var
的想法。
函數的參數列表底層實現實際上是元組,因此只要元組結構和函數參數列表相同,你能夠直接用元組來代替參數列表。就拿剛纔的 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 上的痛苦。
延伸閱讀:若是你想要了解更多該變動背後的故事,請閱讀函數標籤一致性的一些想法。
讓咱們來建立一個按鈕,並給它添加一個點擊事件(不須要界面支持,直接使用 playground 就行):
// 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 的 assistant editor 中進行顯示。
**注意**:你須要在 Xcode 菜單欄上的 View -> Assistant Editor -> Show Assistant Editor 來開啓 assistant editor。
建立點擊的觸發事件,能在用戶點擊按鈕時,觸發綁定的事件——這須要基類爲 NSObject
,由於 selector 僅對 Objective-C 的方法有效。
聲明按鈕,並配置相關屬性。
聲明視圖,給定合適的大小,將按鈕添加到視圖上,最後顯示在 playground 的 assistant editor 中。
讓咱們來看下給按鈕添加事件的代碼:
button.addTarget(responder, action: "tap", forControlEvents: .TouchUpInside)
這裏按鈕的 selector 仍是寫的字符串。若是字符串拼寫錯了,那程序會在運行時因找不到相關方法而崩潰。
爲了解決編譯期間的潛在問題,Swift 3 將字符串 selector 的寫法改成了 #selecor()
。這將容許編譯器提早檢查方法名的拼寫問題,而不用等到運行時。
button.addTarget(responder, action: #selector(Responder.tap), for: .touchUpInside)
延伸閱讀:若是你想要了解更多該變動背後的故事,請閱讀 Doug Gregor 的觀點。
以上就是關於移除特性的所有內容。接下來,讓咱們來看看語言現代化的一些亮點。
這個特性和上一個很類似,可是這是用在鍵值編碼(KVC)與鍵值觀察(KVO)上的:
class Person: NSObject { var name: String = "" init(name: String) { self.name = name } } let me = Person(name: "Cosmin") me.valueForKeyPath("name")
首先建立了 Person
類,這是 KVC 的首要條件。而後用指定的構造器初始化一個 me
,最後經過 KVC 來修改 name
。一樣,若是 KVC 中的鍵拼寫錯誤,這一切就白瞎了 ?。
幸運的是,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 的觀點。
NS
前綴咱們先來看看有 NS
前綴時的寫法,下面是一個典型的 JSON 解析例子(若是對 NS
前綴的前世此生感興趣,請移步):
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
仍是 .pi
下面是一個已知半徑求圓周長的例子:
let r = 3.0 let circumference = 2 * M_PI * r let area = M_PI * r * r
在舊版本的 Swift 中,咱們使用 M_PI
常量來表示 π。而在 Swift 3 中,π 整合爲了 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(GCD)多用於解決網絡請求時,阻塞主線程的 UI 刷新問題。這是用 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 是一個至關強大的繪圖框架,可是和 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
的 View
類,重寫 drawRect()
方法來重繪 view 的內容。
在 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)
注意:在 view 調 drawRect()
方法以前,上下文均爲 nil
,因此使用 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 中,一樣對相關的 enumerate()
方法名作出了名詞性的修改——一樣加上了 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)
延伸閱讀:更多關於命名約定的信息,請查看 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()
Swift 3 將枚舉成員當作屬性來看,因此使用小寫字母開頭而不是之前的大寫字母:
.System
改成 .system
.TouchUpInside
改成 .touchUpInside
.FillStroke
改成 .fillStroke
.CGColor
改成 .cgColor
在 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 項目版本遷移方面能幫到你。
文章的全部代碼我都放在了這個 Playground 中,我已經在 Xcode 8 beta 版本中進行了測試。因此,請確保使用 Xcode 8 來進行編譯。
有任何問題,歡迎告知。Happy coding!?
本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg。