做者: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。