1. 程式人生 > >Swift 3.0 中的新變化

Swift 3.0 中的新變化

本文翻譯自 www.hackingwithswift.com 上釋出的英文文章,原文連結What’s new in Swift 3.0
Swift 3.0 幾乎更改了所有東西,如果不做一些修改的話,你的程式碼很可能不會編譯成功。說真的,如果你覺得從 Swift 1.2 跳到 Swift 2.0 的變化大的話,那些還真的不算什麼。
在這篇文章裡,我會盡可能多的用程式碼示例來解釋那些至關重要的改變,希望這能讓你做好準備升級 Swift 3.0 的最終版。Swift 3.0 的變化比下面列出來的要多得多,但下面這些才是你可能會關心的。
如果你喜歡這篇文章,你可能還會喜歡下面這些:
* What’s new in iOS 10


* What’s new in Swift 2.2
* What’s new in Swift 2.0
* My fress Swfit tutorial series
* Buy Practical iOS 10
* Buy my Pro Swift book

提前警告 #1: 有很多的變動看起來可能是很瑣碎的,我們希望的是這些變化是一次性的,使這門語言在將來的幾年裡趨於穩定,同時也意味著將來的變動會更小。
提前警告 #2: 如果你還沒看過我的《Swift 2.2 裡的新變化(原英文連結:what’s new in Swift 2.2)》,你現在該去看一看了,之前我說過的被棄用的東西,現在都已經移除掉了,包括 ++、–、C 風格的 for 迴圈、元組 splat 語法等等。

所有的函式引數都有標籤了,除非你要求去掉

我們呼叫函式和方法的方式在 Swift 2.0 時就已經變動過了,但這次又變了,而且這一次將會把所有的都破壞掉。在 Swift 2.x 及以前,方法名的第一個引數不需要寫標籤,所以第一個引數的標籤通常會寫到方法名上。例如:

names.indexOf("Taylor")
"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
UIFont
.preferredFontForTextStyle(UIFontTextStyleSubheadline) override func numbeOfSectionsInTableView(tableView: UITableView) -> Int func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

除非你明確指定,否則 Swift 3 中所有的引數都會有標籤,也就意味著方法名不再描述它們的引數了。在實際中,這通常意味著方法名的最後一部分要變成第一個引數的名字。
為了演示會是什麼樣子,下面是 Swift 2.2 程式碼和其對應的 Swift 3 版本:

names.indexOf("Taylor")
names.index(of: "Taylor")

"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
"Taylor".wirte(toFile: "filename", atomically: true, encoding: NSUTF8StringEncoding)

SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
SKAction.rotate(byAngle: CGFloat(M_PI_2), duration: 10)

UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
UIFont.preferredFont(forTextStyle: UIFontTextStyleSubheadline)

override func numberOfSectionInTableView(table: UITableView) -> Int
override func numberOfSection(in tableView: UITableView) -> Int

NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
NSTimer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

這些是你要呼叫的方法,但是,當連續呼叫多個方法時還會有一個連鎖反應:當連線上諸如 UIKit 一類的框架,即使在 Swift 3 中它們依然會遵循沒有第一個引數標籤的舊風格。
下面是 Swift 2.2 中的一些方法簽名:

override func viewWillAppear(animated: Bool)
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(view: SKView)
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(textField: UITextField) -> Bool

在 Swift 3 裡,都需要在第一個引數前面加一個下劃線,來告訴呼叫方(Objective-C 程式碼)不會使用引數標籤:

override func viewWillAppear(_ animated: Bool)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(_ view: SKView)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(_ textField: UITextField) -> Bool

省略不必要的單詞

當 Swift 在 2015 年 12 月開源時,它那嶄新的 API 指南里有這麼一段:”省略不必要的單詞(omit needless words)”。這也引入了 Swift 3 中另一個巨大的改動,因為這意味著方法名中包含的不言而喻的單詞現在已經移除了。
來看一個簡單的例子,首先是 Swift 2.2 的:

let blue = UIColor.blueColor()
let min = numbers.minElement()
attributedString.appendAttributedString(anotherString)
names.insert("Jane", atIndex: 0)
UIDevice.currentDevice()

你能找出其中不必要的單詞嗎?當使用 UIColor 時,blue 將代表一種顏色,所以說 blueColor 是沒有必要的。當你在一個屬性字串(attributedString)上追加另一個的時候,真的還需要說明追加的是一個屬性字串(attributedString)而不是一頭大象嗎?
這是 Swift 3 中相同的程式碼:

let blue = UIColor.blue()
let min = numbers.min()
attributedString.append(anotherString)
names.insert("Jane", at: 0)
UIDevice.current()

正如你所見,這讓方法名明顯變短了!
這種變動對幾乎影響了字串的方方面面。說明這個的最好方法就是並排對比修改前後的程式碼,因此下面每一對程式碼的第一行是 Swift 2.2 版本,第二行是 Swift 3.0:

" Hello ".stringByTrimingCharactersInset(.whitespaceAndNewlineCharacterSet())
" Hello ".trimingCharacters(in: .whitespaceAndNewlines)

"Taylor".containsString("ayl")
"Taylor".contains("ayl")

"1,2,3,4,5".componentsSeparatedByString(",")
"1,2,3,4,5".components(separatedBy: ",")

myPath.stringByAppendingPathComponent("file.txt")
myPath.appendingPathComponent("file.txt")

"Hello, world".stringByReplacingOccurrencesOfString("Hello", withString: "Goodbye")
"Hello, world".replacingOccurrences(of: "Hello", with: "Goodbye")

"Hello, world".substringFromIndex(7)
"Hello, world".substring(from: 7)

"Hello, world".capitalizedString
"Hello, world".capitalized

注意: capitalized 仍然是一個屬性, 但是 lowercaseStringuppercaseString 卻變成了 lowercased()uppercased() 方法。
到目前為止我選的這些例子是由於它們的變化不算太大,但還是有一些重要的變動足以讓我的大腦宕機 – 通常是由於這些方法名太短以至於不太顯而易見。
舉個例子,來看下面這段程式碼:

dismiss(animated: true, completion: nil)

我第一次看到它時,我懵了:”dismiss 什麼?”,這差不多就是適應了 iOS 程式設計這麼久後不可避免的斯德哥爾摩綜合正的表現,但是一旦你學會調換引數標籤變化,重新新增不必要的單詞,就會看到它等效的 Swift 2.2 程式碼:

dismissViewControllerAnimated(true, completion: nil)

實際上 completion: nil 部分現在是可選的了,你直接可以這麼寫:

dismiss(animated: true)

同樣的變化也發生在了prepareForSegue()上,現在看起來是這樣的:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)

列舉屬性的大駝峰現在替換成了小駝峰

雖然語法上無關緊要,我們用來命名類、結構體、屬性、列舉的大寫字母一直大體上遵循這樣的慣例:類、結構體和列舉用大駝峰(MyStruct,WeatherType.Cloudy),屬性和引數名用小駝峰(emailAddress, requestString)。
我之所以說”大體上”是因為還有一些例外情況在Swift 3 裡不再例外了: 屬性和引數首字母大寫的在Swift 3 裡現在要用小駝峰了。
有時這也不是特別陌生:Swift 2.2 用 NSURLRequest(URL: someURL) 來建立 NSURLRequest 物件 - 注意大寫字母 “URL”。Swift 3 裡重寫成了 URLRequest(url: someURL) 同時也意味著你可以使用 webView.request?.url?.absoluteString 來讀取 webView 上的 URL 地址。
更礙眼的一種情況就是當屬性名裡有一部分是大寫的。比如CGColor 或者 CIColor,是的,你猜對了:它們在 Swift 3 裡變成了cgColorciColor ,所以你可以這麼寫:

let red = UIColor.red.cgColor

這些變化確實有助於提高一致性:所有的屬性和引數都需要沒有例外的由小寫字母開頭。
同時列舉物件同樣要變,從大駝峰變成了小駝峰。這也說得過去:列舉是值型別的(就像結構體),但列舉值更接近屬性。然而,這也意味著無論之前你在哪用過 Apple 的列舉,現在都是小駝峰了。所以:

UIInterfaceOrientationMask.Portrait // 舊的
UIInterfaceOrientationMask.portrait // 新的

NSTextAlignment.Left // 舊的
NSTextAlignment.left // 新的

SKBlendMode.Replace  // 舊的
SKBlendMode.replace  // 新的

你懂了吧。然而,這個小變化還帶來了一些更大的改動,由於 Swift 的可選型別在底層實際上就是一個列舉,就像這樣:

enum Optional {
    case None
    case Some(Wrapped)
}

這意味著,如果你用過 .Some 來使用可選型別,你就得改成.some了。當然,你也可以借這個機會徹底拋棄 .some - 下面這兩段程式碼是等效的:

for case let .some(datum) in data {
    print(datum)
}

for case let datum? in data {
    print(datum)
}

C 函式的 Swift 風格引入

Swift 3 裡引入了針對 C 函式的特性來讓庫作者們指定他們的程式碼引入到 Swift 中新的優雅方式。例如,所有以 “CGContext”開頭的函式現在對映到了一個 CGContext 物件的成員方法,使其更符合 Swift 的語言習慣,是的,這意味著像 CGContextSetFillColorWithColor() 這樣醜陋的痣終於被切除了。
為了演示,下面是一個 Swift 2.2 的例子:

let ctx = UIGraphicsGetCurrentContext()

let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
CGContextSetFillColorWithColor(ctx, UIColor.redColor().CGColor)
CGContextSetStrokeColorWithColor(ctx, UIColor.blackColor().CGColor)
CGContextSetLineWidth(ctx, 10)
CGContextAddRect(ctx, rectangle)
CGContextDrawPath(ctx, .FillStroke)

UIGraphicsEndImageContext()

Swift 3 裡,CGContext 可以被當成一個物件來看待,你可以呼叫方法而不是一遍又一遍地重複 CGContext。所以我們的程式碼可以重寫成這樣:

if let ctx = UIGraphicsGetCurrentContext() {
    let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
    ctx.setFillColor(UIColor.red.cgColor)
    ctx.setStrokeColor(UIColor.black.cgColor)
    ctx.setLineWidth(10)
    ctx.addRect(rectangle)
    ctx.drawPath(using: .fillStroke)

    UIGraphicsEndImageContext()
}

注意:在 Swift 2.2 和 Swift 3.0 UIGrapicsGetCurrentContext() 都會返回一個可選的 CGContext ,但是因為 Swift 3 用的是方法呼叫,所以使用它之前我們需要安全地拆包。
這種 C 函式的對應同樣存在於別處,例如,你現在可以讀取CGPDFDocument 中的 numberOfPages屬性了,並且 CGAffineTransform 也被改造的特別明顯,下面的例子展示了新舊語法的對比:

CGAffineTransformIdentity
CGAffineTransform.identity

CGAffineTransformMakeScale(2, 2)
CGAffineTransform(scaleX: 2, y: 2)

CGAffineTransformMakeTranslation(128, 128)
CGAffineTransform(translationX: 128, y: 128)

CGAffineTransformMakeRotation(CGFloat(M_PI))
CGAffineTransform(rotationAngle: CGFloat(M_PI))

動詞和名詞

這一部分人們會開始有些困惑,但這確實很重要的部分。
下面是一些 Swift API 指南里的引用:
* “When the operation is naturally described by a verb, use the verb’s imperative for the mutating method and apply the “ed” or “ing” suffix to name its nonmutating counterpart”(當一個操作可以用一個自然地動詞來描述,直接使用這個動詞的命名這個操作的可變方法,並新增 “ed” 或 “ing” 字尾來命名來命名對應的不可變方法)
* “Prefer to name the nonmutating variant using the verb’s past participle”(偏向於用動詞的過去分詞命名方法的不可變變種)
* “When adding “ed” is not grammatical because the verb has a direct object, name the nonmutating variant using the verb’s present participle”(當由於這個動詞有一個直接賓語而新增”ed” 不符合語法時,使用動詞的現在分詞命名方法的不可變變種)
* “When the operation is naturally described by a noun, use the noun for the nonmutating method and apply the ‘form’ prefix to name its mutating cunterpart”(當一個操作可以用一個自然的名詞來描述時,使用名詞命名不可變方法並且新增字首 ‘form’來命名方法的可變版本)
明白了嗎?不要驚訝於 Swift 的命名規則竟然用語言學屬於來表述 - 畢竟這也是一門語言啊! - 但這至少可以讓我對自己的英語學位沾沾自喜。這意味著很多方法的命名將會有難以理解細微的調整。
我們來看幾個簡單的例子:

myArray.enumerate()
myArray.enumerated()

myArray.reverse()
myArray.reversed()

每一次 Swift 3 裡在方法名裡追加一個 ‘d’:這就是一個被返回的值。
大多數情況這些變化沒什麼影響,但是當對陣列排序時就會產生困惑。Swift 2.2 用 sort() 來返回一個排好序的陣列,用 sortInPlace() 在原來的陣列上進行排序。在 Swift 3.0 裡,sort() 重新命名成了 sorted()(根據上面的例子),而 sortInPlace() 則重新命名成了 sort()
你要注意了,Swift 2.2 的 sort() 會返回一個排好序的陣列,而在 Swift 3.0 裡 sort() 直接對陣列進行排序

這些改動是為了什麼?

這些改動都容易看懂,其中一些的改動很細微但是造成的破壞卻是嚴重的,可以想象蘋果的 Swift 工程師只是讓我們的生活更艱難了。然而,事實是他們在非常努力的讓 Swift 變得易學、易用並且儘可能地快,這是他們的首要任務。特別是,我已經被蘋果團隊所承諾的作為社群努力的一部分,確保他們所做的改動都是經過公開討論並同意的而打動了。上面所說的每一個變化都經過了社群大範圍的討論才可以加入到 Swift 3.0,這絕對是一件不可思議的事。
你也可以參與進來幫助改進這些變化,他們熱衷於聽取廣大使用者的想法,這也意味著 Swift 的未來真的在你的手中