文/mango_To(简书作者)
原文链接:http://www.jianshu.com/p/dc80e290806f
前言
Swift 3今年晚些时候会与大家见面,它会带给Swift开发者巨大的代码层面的改变。
如果你最近没有跟进Swift Evolution的步伐,你也许会问到底有啥变化,它会怎样影响你的代码,以及你什么时候应该迁移到Swift 3上来。那么这篇文章会为你一一解答。
在这篇文章中,我会着重强调Swift 3中最重要的变化,因为这些会影响你的代码。我们一起来深掘吧!
开始
现在Swift 3预览版已经可以在Xcode 8 beta版使用了。尽管Swift 3的进化之路就要接近尾声,但是在接下来的几个月还是会做出一些变化。Swift 3的特性最终确定下来是在2016年,Xcode 8 正式版本发布的时候,所以你不得不推迟采用Swift 3编写的 App的发布日期。
为了让开发者能够按照他们自己的方式迁移到Swift 3上来,作为一个小的升级包,Xcode 8会包含Swift 2.3。从开发者的角度看,Swift 2.3跟Swift 2.2是一样的,只不过它会支持更多新的SDKs和WWDC上宣布的Xcode新特性。一旦Xcode 8 发布正式版,且你还没有迁移到Swift 3上来,那么你可以提交用Swift 2.3编写的App。
我建议你在playground中测试我们要讨论的特性,还有你也许可以在你的一个工程里运行迁移助手(还是不要拿公司项目做小白鼠了?),来感受一下新技术带来的变化。因为直到Xcode 8 正式版发布,Swift 3最终确定下来,你才能发布app,所以你该考虑等到所有事情有了定论再迁移到Swift 3上来。
迁移到Swift 3
当转换到Swift 3,你会发现几乎所有文件都需要改变!这主要是因为Cocoa API命名发生了改变。或者更准确的说,API相同的,但是现在OC,Swift的命名不统一。Swift 想要在接下来的时间让Swift的书写更加自然。
苹果在Xcode 8 中集成了迁移助手,它可以智能地帮助你修正这些变化。但是不要惊讶,如果你想要自行修改一下地方,那么迁移助手是不会自动帮你处理的。
你可以很快地转换到Swift 2.3 或者 Swift 3上来。如果你想转换回来,你可以在Xcode这么操作:Edit > Convert > To Current Swift Syntax ... 。编译器像迁移助手一样智能,如果你在一个方法的调用上使用了旧的API,编译器会提供一个可选选项帮助你修正,这样你就能使用正确的现代的API了。
最重要的消息是:Swift 3目标是成为做出重大变化的最后一个版本。所以当Swift从一个版本迭代到另一个版本时,你能够保持自己的代码不变。尽管Swift 核心团队不能预测未来,但是他们承诺如果真的需要破坏源兼容性,他们会提供一个相当长的废弃周期。那就意味着Swift语言具有了源稳定性,这会促进更多保守公司采用Swift语言。
据说,二进制稳定性的目标还没有实现。在文章结尾,你会了解更多二进制稳定性的影响。
已经实现的---Swift进化提议
自从Swift开源,社区成员对于Swift的改变,给出了上百条提议。大多数提议(目前70条,或者更多)在讨论后都被接受了。那些被拒的提议也是在经过激烈的讨论后,做出的决定。无论如何,最终Swift核心团队会对所有的提议做出决定。
Swift核心团队与社区展开了广泛的互动交流。事实上,Swift在Github上获得了3万多颗星。日复一日,每周都有好几条提议被提出。甚至苹果的工程师想要做出改变时,也会在Github开出新的仓库,给出提议。
在以下部分,你会看到诸如[SE-0001]的标签。这些是是Swift Evolution 提议编号。这里的有编号的提议都是已被接受,会在Swift 3中实现。同时也会提供每一个提议的链接,你可以查看每一个改变的具体细节。
API 变化
Swift 3最大的更新是标准库采用了统一的命名规范。API 设计指南包含了团队构建Swift 3遵守的规范,这对于编写可读性,易维护性代码具有很重的价值。核心团队实践这样的准则:好的API设计总是考虑到调用场景。他们努力让API的用途变得更加清晰。废话不多说了,接下来是最有可能影响到你的变化。
第一个参数标签
我们以一个我们每天都在使用的实例开始。
函数和方法中的第一个参数会需要一个便签,除非你有其他方面的需求。之前,当你调用一个函数或者方法时,你是省略第一个参数标签的[SE-0046]:
//第一条为Swift 2中旧的方式, 下一条是Swift 3中新的方式
"RW".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
"RW".write(ToFile:"filename", atomically: true, encoding: String.Encoding.uft8)
SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
SKAction.rotate(byAngle: .pi / 2, duration: 10)
UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
UIFont.preferredFont(forTextStyle: UIFontTextStyleSubheadline)
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
override func numberOfSections(in tableView: UITableView) -> Int
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
func viewForZooming(in scrollView: UIScrollView) -> UIView?
NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(reset), userInfo: nil, repeats: true)
Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(reset), userInfo: nil, repeats: true)
注意方法定义是怎么为外部名称使用诸如:"of", "to", "with", 和 "in"等介词的。
如果方法调用没有介词,不需要标签的话,你可以显式地用下划线替换第一个参数名。
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
override func didMoveToView(_ view: SKView) { ... }
在很多编程语言中,方法会有一个基础的名字并提供不同的参数名。Swift也不例外,现在你会经常碰到重载方法,因为API更加直接。这里有一个例子,表现index()方法的两种形式:
let names = ["Anna", "Barbara"]
if let annaIndex = names.index(of: "Anna") {
print("Barbara‘s position: \(names.index(after: annaIndex))")
}
总之,参数名的变化让方法名更加统一,也更加容易学习。
省略不必要的单词
在之前的苹果库的版本迭代中,方法会包括一个名字,用来指示返回值。得益于Swift编译器的类型检查,这显得不是很必须。团队认真审视后过滤掉了这些噪音只留下信号,结果很多重复的单词都被移除了。
API在OC库转换到原生Swift方面更加智能 [SE-0005]:
//第一条为Swift 2中旧的方式, 下一条是Swift 3中新的方式
let blue = UIColor.blueColor()
let blue = UIColor.blue
let min = numbers.minElement()
let min = numbers.min()
attributedString.appendAttributedString(anotherString)
attributedString.append(anotherString)
names.insert("Jane", atIndex: 0)
names.insert("Jane", at: 0)
UIDevice.currentDevice()
UIDevice.current
现代化 GCD 和 Core Graphics
说到对旧的API的反对,GCD和Core Graphics成了最需要被改造的两个。
GCD用在多线程任务中,例如:耗时的计算工作或者与服务器的通信。通过切换任务到不同的线程,可以避免阻塞用户线程。libdispatch库是用C语言写的,而且采用C语言风格的API。现在这些API被以原生Swift的风格重新设计。
// Swift 2 旧方式
let queue = dispatch_queue_create("com.test.myqueue", nil)
dispatch_async(queue) {
print("Hello World")
}
// Swift 3 新方式
let queue = DispatchQueue(label: "com.test.myqueue")
queue.async {
print("Hello World")
}
相似的,Core Graphics 也是用C写的,过去使用了非常糟心的函数调用。可以看一下新方法的样子[SE-0044]:
// Swift 2 旧方法
let ctx = UIGraphicsGetCurrentContext()
let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
CGContextSetFillColorWithColor(ctx, UIColor.blueColor().CGColor)
CGContextSetStrokeColorWithColor(ctx, UIColor.whiteColor().CGColor)
CGContextSetLineWidth(ctx, 10)
CGContextAddRect(ctx, rectangle)
CGContextDrawPath(ctx, .FillStroke)
UIGraphicsEndImageContext()
// Swift 3 新方法
guard let ctx = UIGraphicsGetCurrentContext() else {
return
}
let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
ctx.setFillColor(UIColor.blue.cgColor)
ctx.setStrokeColor(UIColor.white.cgColor)
ctx.setLineWidth(10)
ctx.addRect(rectangle)
ctx.drawPath(using: .fillStroke)
UIGraphicsEndImageContext()
枚举 Cases 首字母
另一个与你已经习惯的Swift编码方式不同是,枚举case 现在使用lowerCamelCase。这会让他们与其他属性更加一致 - [SE-0006]:
//第一条为Swift 2中旧的方式, 下一条是Swift 3中新的方式
UIInterfaceOrientationMask.Landscape
UIInterfaceOrientationMask.landscape
NSTextAlignment.Right
NSTextAlignment.right
SKBlendMode.Multiply
SKBlendMode.multiply
UpperCamelCase现在只在类型名和协议名上有保留。这可能要花上一段时间来适应,但是Swift团队这么做有他的理由---他们在努力达成一致性。
有返回值的函数和修改自身的函数
Swift标准库在用名词,动词的函数命名会越来越统一。你依据动作发生带来的影响来命名一个函数。经验法则是:如果它包含一个像"-ed" 或者"-ing"的后缀,那么认为这是一个名词函数。名词函数会返回一个值。如果它没有后缀,那么它很可能是一个必要的动词。这些"动词"函数在引用内存执行动作。这也被称为modifying in place。以下是遵守名词/动词规则的函数对。这里还有一些函数对[SE-0006]:
customArray.enumerate()
customArray.enumerated()
customArray.reverse()
customArray.reversed()
customArray.sort() // changed from .sortInPlace()
customArray.sorted()
这里是一些行为上差异:
var ages = [21, 10, 2] //变量,可以修改
ages.sort() //modify in place(这个翻成就地修改?), 值现在是[2, 10, 21]
for (index, age) in ages.enumerated() { //"-ed" 名词 返回一个copy。
print("\(index), \(age)") //1,2 \n 2,10 \n 3,21
}
函数类型
函数声明和函数调用总是需要括号包括参数:
func f(a: Int) { ... }
f(5)
然而,当你用函数类型作为参数时,你可能会这么写:
func g(a: Int -> Int) -> Int->Int { ... }//swift 2中旧方法
你可能注意到这非常难读。参数在哪里结束?返回值在哪里开始?在Swift 3中正确的定义这个函数是 [SE-0066]:
func g(a:(Int) -> Int) -> (Int) -> Int { ... }//swift 3 新方法
现在的参数列表是括号包围,随后是返回类型。这样函数变得更加清晰,结果是,函数类型变得更容易识别。
这里有一些健壮性的比较:
//swift 2中旧方法
Int -> Float
String -> Int
T -> U
Int -> Float -> String
//Swift 3中 新方法
(Int) -> Float
(String) -> Int
(T) -> U
(Int) -> (Float) -> String
新增API
尽管Swift 3最大的更新是对现存API的修改,但是Swift社区也在添加新的API方面,做了大量的工作。
获取包含类型(或者说是隐含类型)
当你定义一个静态属性或者方法时,你总是通过类型直接调用他们:
CustomStruct.StaticMethod()
如果你在一个类型的中编写代码,你还需要一个类型名来调用静态方法。为了更加清晰,现在你可以调用Self来获取隐含类型。大写字母"S"获得self的类型,小写字母"s"获取self的实例。
这里会解释工作原理[SE-0068]:
struct CustomStruct {
static func staticMethod() {}
func instanceMethod() {
Self.staticMethod() //在类型体内部, 之前要写成CustomStruct.staticMethod()
}
}
let customStruct = CustomStruct()
customStruct.Self.staticMethod()
注:这个特性会被加到 Swift 3, 但是目前在Xcode 8 beta 5中还不能用。
内联序列
sequence(first: next:)和sequence(state: next:)是全局函数他们返回无限序列。你可以给他们一个初始值,或者初始状态,他们会以懒加载的方式应用到一个闭包[SE-0094]:
for view in sequence(first: someView, next: {$0.superView}) {
//someView, someView.superView, someView.superView.superView, ...
}
你可以通过添加前缀操作符,来限制序列
for x in sequece(first: 0.1, next: {$0 * 2}).prefix(while:{$0 < 4}) {
//0.1, 0.2,0.4,0.8,1.6,3.2
}
注:这个特性会被加到 Swift 3, 但是目前在Xcode 8 beta 5中还不能用。
其他零碎的变化
- #keyPath() 像 #selector()一样工作,这样可以避免在使用字符串类型的APIs,打错字。
- 你现在可以通过你想要使用的类型调用pi, 例如: Float.pi, CGFloat.pi。大多数时候,编译器能够推断出类型:let circumreference = 2 * .pi * radius[SE-0067]
- 旧函数类型的前缀NS被移除。你现在可以使用Calendar, Date 替代 NSCalendar和NSDate。
工具上的提升
Swift是一门语言,编写Swift代码要用到开发环境-苹果开发者就是Xcode了!工具的变化将会影响到你每天怎么编写代码。
Swift 3修复了编译器和集成开发环境中的bugs。也提高了错误和警告信息的精度。如你所料,每一次发布新版,Swift运行和编译速度都变得更快了。
- 通过改善字符串哈希,字符串类型字典速度提升3倍。
- 把对象的存储从堆转移到栈,速度提升了24倍(在某些情况下)
- 编译器现在一次缓存多个文件(在整个模块的优化)
- 代码体积优化降低了Swift代码编译后的体积。苹果的demo-Demobots编译后的体积降低到原来的77%。
Xcode正在学习以原生Swift的方式进行思考:
- 当你右击API方法像sort(),会跳到方法的定义,你通常会被带到奇怪的头文件里。在Xcode 8中,如你所料它是Array的一个扩展方法。
Swift快照像是Swift Evolution的后续版本。在完全集成进Xcode前,他们提供一个机会来使用新的语法。Xcode 8可以在Playground中加载并运行Swift快照。
Swift包管理器
Swift源码实际上是几个仓库的集合,包括:语言,核心库,还有包管理器。此套件一起构成了我们所认识的Swift。 Swift包管理器为你的分享代码和工程代码定义了一个简单的目录结构。
和你所熟悉的Cocoapods或者Carthage相似,Swift包管理器会下载依赖,并编译,链接他们来创建库和可执行文件。Swift 3是支持Swift包管理器的第一个版本。目前,有1000多个库已经支持包管理器了,而且在接下来的几个月,会有更多的库加入进来。
预计未来的特性
之前提到过,Swift 3 通过避免重大变化,让你能够在Swift版本迭代中保持你的代码。但是事实上,一些崇高的目标还有没有达到,即泛型添加和ABI稳定。
泛型添加包括递归协议约束和让一个受约束的扩展遵守一个新的协议(例如:由遵守Equatable的元素组成的数组也遵守Equatable协议。)在这些特性完成前,Swift不能宣布ABI稳定。
ABI稳定允许不同版本的Swift应用,库一起编译,链接,和相互通信。ABI稳定对于第三方库不提供源码情况下组装框架至关重要,因为Swift新版本要求他们不仅要更新代码,还要重新构建框架。
此外,就目前Xcode创建的iOS和macOS应用来看, ABI稳定消除了组装Swift标准库和二进制文件的需求。现在二进制文件捆绑着2M额外大小的文件,这确保他们可以运行在未来的操作系统上。
所以总而言之,你可以在Swift版本迭代中保持你的源码,但是版本之间,编译后的二进制文件的兼容问题还没有解决。
总结
随着社区的不断实践,Swift持续发展。虽然还在起步阶段,但是这门语言有很大动力和明朗的未来。Swift已经运行在Linux上了,而且在未来几年内,你很有可能看到它运行在服务器端。从头设计一门语言有它的优势,一旦ABI稳定下来,想要再打破它的可能性会非常小。现在正是更正语言的好机会,绝不后悔。(哈,swift 3是多么被寄予厚望呀!??)
Swift正在扩张。苹果也在使用他们自己的产品。苹果团队在音乐APP,控制台,Sierra画中画,Xcode文档查看器和ipad版的Swift Playgroud中使用了Swift。
说到这里,应该会有一个很大的推动力促使非程序人员通过iPad或者教育计划学习Swift。
这里的关键是Swift还在不断的上升:命名变得越爱越好,代码变得越来越清晰,还有工具帮助你做迁移。如果你深受启发,想要深入挖掘,你可以观看 WWDC session videos.