Swift 3.0 中的新变化

Posted on Posted in iOS, Swift

本文翻译自 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: Swift 3.0 还在开发当中,新的变动发布时这篇文章会及时地更新
提前警告 #2: 有很多的变动看起来可能是很琐碎的,我们希望的是这些变化是一次性的,使这门语言在将来的几年里趋于稳定,同时也意味着将来的变动会更小。
提前警告 #3: 如果你还没看过我的《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 的未来真的在你的手中

发表评论

电子邮件地址不会被公开。 必填项已用*标注