本文翻译自 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",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,repeats: true)
NSTimer.scheduledTimer(timeInterval: 0.35,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,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
仍然是一个属性, 但是 lowercaseString
和 uppercaseString
却变成了 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 里变成了cgColor
和 ciColor
,所以你可以这么写:
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,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 的未来真的在你的手中