前言
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]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//第一条为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”等介词的。
如果方法调用没有介词,不需要标签的话,你可以显式地用下划线替换第一个参数名。
1 2 |
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... } override func didMoveToView(_ view: SKView) { ... } |
在很多编程语言中,方法会有一个基础的名字并提供不同的参数名。Swift也不例外,现在你会经常碰到重载方法,因为API更加直接。这里有一个例子,表现index()方法的两种形式:
1 2 3 4 |
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]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//第一条为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的风格重新设计。
1 2 3 4 5 6 7 8 9 10 11 |
// 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]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 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]:
1 2 3 4 5 6 7 8 9 |
//第一条为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]:
1 2 3 4 5 6 7 8 |
customArray.enumerate() customArray.enumerated() customArray.reverse() customArray.reversed() customArray.sort() // changed from .sortInPlace() customArray.sorted() |
这里是一些行为上差异: