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 的未来真的在你的手中