今年下半年,Swift 3 就要正式发布了。对于任何一位 Swift 开发人员来说,都会给其代码带来很大的改变。
如果还没有密切关注过 Swift Evolution 的话,你可能会好奇究竟引入了什么新内容,这些新内容会如何影响自己的代码,还有什么时候要把代码转换成 Swift 3 版本,本文会回答这些问题!
在这篇文章中,我将重点阐述 Swift 3 中会给代码带来影响的最重要的变化。就让我们来一探究竟吧!
今天 Swift 3 预览版在 Xcode 8 Beta 上可以使用了,Swift 3 的演进过程即将落幕。在未来几个月内仍然可能会有一些变化。2016 年底,Xcode GM 版(测试版的一种,属于如果没有发现问题就作为正式版发布的版本。一般情况下 GM 版随后都会变为正式版发布,所以也有人在正式版发布之前将 GM 版当正式版安装使用)诞生的时候,Swift 3 的功能特性就会稳定下来。所以我们要把 Swift 3 编写的 App 推迟到那个时候再发布到 App Store。
为了让开发人员能够自己迁移到 Swift 3,苹果公司已经将 Swift 2.3 作为小的更新与 Xcode 8 捆绑在一起了。对于开发人员来说,Swift 2.3 与 Swift 2.2 比较,前者支持很多在 WWDC 上宣布的新的 SDK,还有 Xcode 新的功能特性。一旦 Xcode 8 的 beta 版发布,又没有把代码迁移到 Swift 3 的话,那么我们就可以使用 Swift 2.3 开发并提交应用了。
我建议在 playground 里尝试一下我们在讨论的新特性,还可以使用苹果的 Migration Assistant 功能,把一切更改都直接拿过来用。但是由于 Xcode 8 要升级到 beta 版本,或者要等到 Swift 3 最终发布出来,否则我们无法向 App Store 提交 App,所以我们可能会等到这一切都定下来以后,才会把代码迁移到 Swift 3。
将代码迁移到 Swift 3 的时候,你会发现几乎每个文件都需要修改!很大程度上是由于所有 Cocoa API 的名称都已经发生了变化。更确切地说,API 是一样的,一个名字用于 Objective-C,另一个名字用于 Swift。未来几年,Swift 3 的风格会变得更自然。
苹果的 Migration Assistant 功能可以出色地一次性搞定这些变化。虽然有些地方在迁移的过程中无法自动进行处理,需要我们自己修改,但这是很正常的事情。
我们可以直接将代码转化为 Swift 2.3,也可以转化为 Swift 3。如果想要回到之前的某个版本,可以在 Xcode 中使用 Edit> Convert > To Current Swift Syntax…来做版本转换。幸运的是,这个功能像 Migration Assistant 一样智能。如果我们不小心调用了旧有的 API,编译器会给出“Fix-It”这样的提示,可以帮助我们使用当前正确的 API。
最好的消息是 Swift 3 旨在成为源码级修改的最终版本。展望未来,我们应该能够避免对 Swift 代码做版本转换。虽然Swift语言的核心开发团队无法对未来的变化作出预测,但他们已经承诺,如果要打破源码的兼容性的话,他们会对旧有的 API 提供较长的弃用时间。这也就意味着 Swift 达到源码稳定的程度,会鼓励更多思想保守的公司尽可能地去使用。
这也同时表明,Swift 语言还没有达到二进制源码稳定的程度。读完这篇文章以后,你会发现这种不稳定性带来的更多影响。
自从 Swift 开源以来,开发者社区的成员已经提交超过 100 份修改意见。大量(迄今为止 70 项)的修改意见经过讨论和修改后被采纳。但也有一些被驳回的建议引发了激烈的讨论。最后,核心开发团队对所有建议做出最终的决定。
Swift 核心开发团队与广大的开发者社区之间的合作令人印象深刻。事实上,Swift 项目在 GitHub 已经获得了 3 万颗 star。每一周都有新的修改意见被提交,周而复始。苹果开发工程师甚至把要修改的内容在 GitHub 上创建开放的 repository。
在本文接下来的章节中,我们会看到带有[SE-0001]标签样子的链接。这些数字都是 Swift Evolution 的编号。这里是已经被采纳的修改建议,在最终版本 Swift 3.0 中会被实现。每项修改建议的链接都有了,因此我们可以找到每项特定修改的全部细节内容。
Swift 3 中最显著的更新包括对标准库采用一致的命名规则。 API Design Guidelines 包含核心开发团队构建 Swift 3 时所确定的一些规则,可以为新手带来很好的可读性和易达性(accessibility)。核心开发团队摒弃了“好的API设计总是要考虑调用现场(call site)”这一要领,努力在用法上清晰明了。闲话少说,下面来看看最有可能对我们产生影响的变化吧。
我们一起每天使用Swift语言,开始进行强制的转换练习。
函数和方法的第一个参数总会用一个标签表示,除非你想故意去掉。在这之前,调用函数或方法的时候,第一个参数的标签是被省略掉的[ SE-0046 ]:
// old way, Swift 2, followed by new way, Swift 3 "RW".writeToFile("filename", atomically:true, encoding: NSUTF8StringEncoding) "RW".write(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 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) NSTimer.scheduledTimer(timeInterval:0.35, target:self, selector:#selector(reset), userInfo:nil, repeats:true)
注意在定义方法外部名称的时候,所使用的介词,如“of”,“to”,“with”和“and”。这些都是为了提高可读性。
如果调用方法时不需要介词,也不需要第一个参数的标签也具有很好的可读性,我们就要使用下划线明确地去掉第一个参数名:
override func tableView(_tableView: UITableView, numberOfRowsInSec-tion 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 语言编译器具有类型检查功能,因此这样的函数表示方法就没有太大必要了。Swift 核心开发团队尽量去掉没有意义的部分,而保留有必要存在的内容。因此很多冗余的部分被省略掉了。
将 Objective-C 的库转换到原生的 Swift 变得更便捷了[ SE-0005 ]:
// old way, Swift 2, fllowed by new way, Swift 3 let blue = UIColor.blueColor() let blue = UIColor.blue() let min = numbers.minElement() let min = numbers.min() attributedString.appendAttributedString(anotherString) attrubutedString.append(anotherString) names.insert("Jane", atIndex: 0) names.insert("Jane", at:0) UIDevice.currentDevice() UIDevice.current()
说到旧有 API 一贯的样式,GCD 和 Core Graphics 都急迫需要做较大的调整。
Grand Central Dispatch(GCD)被用在许多线程级任务上,比如长时间的计算或与服务器之间的通信。通过线程之间的切换,我们可以避免用户界面被卡住。Libdispatch 这个库是用 C 语言编写的,也一直使用 C 风格的 API。如今,原生 Swift 语言的 API 则被再次定义 [ SE-0088 ]:
//old way, Swift 2 let queue = dispatch_queue_create("com.test.myqueue", nil) dispatch_async(queue){ print("Hello World") } //new way, Swift 3 let queue = DispatchQueue(lable:"com.test.myqueue") queue.async{ print("Hello World") }
同样的,Core Graphics 也用 C 语言编写,在这之前使用一种奇怪的函数调用方式。现在则使用了一种新的方式 [ SE-0044 ]:
// old way, Swift 2 let ctx = UIGraphicsGetCurrentContext() let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512) CGContextSetFillColorWithColor(ctr, UIColor.blueColor().CGColor) CGContextSetStrokeColorWithColor(ctx, UIColor.whiteColor().CGColor) CGContextSetLineWidth(ctx, 10) CGContextAddRect(ctx, rectangle) CGContextDrawPath(ctx, .FillStroke) UIGraphicsEndImageContext() // new way, Swift 3 if let ctx = UIGraphicsGetCurrentContext() { 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() }
Swift 语言中,还有一种与之前编码风格相反的情况,枚举值使用了“小骆驼拼写法”(lowerCamelCase)。这种做法使得这些枚举值与其他属性及属性值看起来更加统一[ SE-0006 ]:
// old way, Swift 2, followed by new way, Swift 3 UIInterfaceOrientationMask.Landscape UIInterfaceOrientationMask.landscape NSTextAlignment.Right NSTextAlignment.right SKBlendMode.Multiply SKBlendMode.multiply
“大骆驼拼写法”(UpperCamelCase)现在只有在数据类型名和协议命名时使用。虽然这可能需要一些时间去适应,但苹果公司的核心开发团队对此所做的努力很合理。
标准库的命名,在动词和名词的使用上也更加一致。根据产生的效果和发生的动作来选定方法名。单凭经验判断,如果名称中是包含像“-ed”和“-ing”这样的后缀,我们就认为方法名是名词,名词性的方法会返回一个值。如果方法名没有这样的后缀,那么就很像是一个祈使句中的动词。这些“动词”方法在所占内存中执行动作。也就是修改了里面的内容。有好几对方法都遵循这一名词/动词命名法的约定。例如[ SE-0006 ]:
customArray.enumerate() customArray.enumerated() customArray.reverse() customArray.reversed() customArray.sort() //changed from .sortInPlace() customArray.sorted()
下面的这小段代码就是例子:
var ages = [21, 10, 2] //variable, not constant, so you can modify it ages.sort() //modified in place, value now [2, 10, 21] for (index, age) in ages.enumerated() { // "-ed" noun returns a 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 {…} //old way, Swift 2
你可能注意到了,这种写法的可读性是非常差的。函数参数在哪里结束,返回值在哪里?Swift 3会使用更好的方式去定义函数[ SE-0066 ]:
func g(a: (Int) -> Int) -> (Int) -> {…} //new way, Swift 3
现在,参数列表被括号括起来了,然后才是返回值类型。这样一切变得更清晰。同时函数类型更容易被识别出来。下面则是更突出的比较:
//old way, Swift 2 Int->Float String->Int T->U Int->Float->String //new way, Swift 3 (Int)->Float (String)->Int (T)->U (Int)->(Float)->String
Swift 3 中最大的改变不光是对现有 API 所作的修改,还有很多 Swift 社区所做的努力,其中也包括 Swift API 中新增加的一些实用的功能。
当定义一个静态属性或方法的时候,我们随时可以直接通过其类型对其进行调用:
CustomStruct.staticMethod()
如果我们编写的是类型内部的代码,仍然需要使用类型名称来调用静态方法。更简单地说,我们可以调用Self得到当前实例所属类型。首字母大写的Self表示实例的类型,而首字母小写的self表示表示实例本身。
下面是个具体的例子[ SE-0068 ]:
struct CustomStruct { static func staticMethod() { ... } func instanceMethod() { Self.staticMethod() // in the body of the type } } let customStruct = CustomStruct() customStruct.Self.staticMethod() // on an instance of the type
注意: 这一特性会被添加在Swift 3中,但是目前在Xcode 8 beta5版本中还不能使用。
函数 sequence(first:next:)
和 sequence(state:next)
都是全局的,会返回一个无限序列。我们给这两个函数传入一个初始值,或者传入一个可变的状态后,函数过会儿就会调用闭包中的代码[ SE-0094
]:
for view in sequence(first: someView, next: { $0.superview }) { // someView, someView.superview, someView.superview.superview, ... }
我们还可以使用 prefix
操作符来为序列添加限制条件[ SE-0045
]:
for x in sequence(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 beta5版本中还不能使用。
#keyPath()
和 #selector()
一样,帮助我们在使用API的时候克服拼写错误。 Float.pi
、 CGFloat.pi
。大多数情况下编译器都可以推断出类型: let circumference = 2 * .pi *radius
[ SE-0067
]: Calendar
和 Date
来代替 NSCalendar
和 NSDate
了。 Swift 是一门编程语言。这样一来,编程很大程度上就会涉及所使用的开发环境——对于苹果开发人员来说,就是 Xcode!编程工具的改进影响着我们每天的编程方式。
Swift 3 修复了编译器和 IDE 中存在的 Bug,使错误和警告信息的精确度更高。正如我们所期望的,每一次发布,Swift 的编译和运行过程都会变得更快:
Xcode 也在逐渐去支持原生的 Swift:
sort()
这样的 API 右键单击后跳转到其定义的时候,会跳到一个匿名的头文件。现在,在 Xcode 8 中,我们会看到 sort()
方法其实是 Array
类的扩展。 开源的 Swift 语言实际上包含了语言本身,核心类库以及包管理器。这些所有内容组成了我们所认识的 Swift。Swift 包管理器( Swift Package Manager )定义了一个简单的目录结构,包括我们所共享和导入到项目中的所有 Swift 代码。
你可能使用过 Cocoapods 或者 Carthage,Swift 包管理器与之类似。包管理器会下载依赖项并编译它们,把这些依赖项链接(link)起来,创建类库和可执行文件。已经有 1000 个类库支持包管理器,在未来的几个月,我们会看到更多支持包管理器的类库。
前面提到过,Swift 3 通过努力避免突发的变化,让我们的代码在不同版本之间保持兼容。如果这种说法成立的话,那么就会有更高阶的目标在这个版本中没有实现,即泛型增强和程序二进制接口(ABI)的稳定性问题。
泛型增强包括协议约束的递归,这能够使一个受约束的扩展符合新的协议(也就是说某个存放 Equatable 对象的数组也要遵守 Equatable 协议)。在这些特性完成之前,还不能说Swift是二进制接口稳定的。
程序二进制接口的稳定会使被编译的程序和类库在 Swift 不同版本之间能够相互被编译连接,相互也能进行交互。这对于不提供源代码的第三方类库由依赖项到库的这一过程至关重要。因为新版本的 Swift 不仅需要这些第三方库修改代码,而且还需要对它们重新进行编译 。
另外,由于目前我们无论创建 iOS 还是 macOS App 都要使用 Xcode,程序二进制接口的稳定性免去了 Swift 标准库与二进制文件之间的捆绑。现在二进制文件额外包含有 2M 的文件大小,用来确保 App 能够在以后的操作系统上运行。
总之,我们现在能够保持源代码版本间的兼容,但无法保证编译后的二进制文件也具有兼容性。
由于开发者社区的改进工作精益求精,使得 Swift 不断向前演变。虽然该语言仍处于起步阶段,却有着不错的势头和前景。Swift 已经可以在 Linux 系统上运行了。在未来几年内,我们很可能还会看到它在服务端运行。全新地设计一门编程语言肯定会有其优越性,因为一旦稳定下来,就很难再有机会破坏程序二进制接口的稳定性了。这是唯一可以不留遗憾地给语言纠错的机会。
Swift 语言也在扩展其应用范围。苹果公司正在身体力行。该公司的开发团队在 Music App、Console、Sierra 的画中画功能,以及 Xcode 的文档查看器和 iPad 新版的 Swift Playground 中都使用了 Swift 语言。
说了这么多,让人感到是在迫使非专业程序员也要来学习 Swift 语言。而从在 iPad 上支持 Swift,到一系列的教学方面的倡议都表明了这一点。
关键是 Swift 在持续改进:命名变得更加规范,代码读起来也更加清晰,迁移代码也有了可用的工具。如果我们打算进步一深入了解的话,可以观看 WWDC 的大会视频 记录。
可以肯定的是,2016 年年底 Swift 3 最终的发布会更加令人期待。我们也会继续在此关注所有的新内容。所以,请继续关注我们的图书发布通告,相关视频和教程。这些令人感到兴奋的变化都会在这些内容中有相应的实践。
你对 Swift 3 的哪些特性最感兴趣?又希望我们最先讲解哪些内容呢?快给我们留言吧!