Swift 3 正式发布已经 3 周了,大家 Swift 项目的代码迁移做的怎么样?Glow Baby 项目我花了近 3 天时间,12956 行增改,9817 行删减,360 个文件。迁移的过程是痛苦的,心很累,Xcode 8 的迁移工具也没有让我轻松多少。
不过待迁移完毕后,Swift 3 读起来、写起来都更舒服。Swift 1 确立了语言的基线:安全、快速、现代。Swift 2 展现了 Swift 应该是什么,未来怎么走:面向协议的编程、开源。而 Swift 3 更多是清扫和规范:新的 API 设计简洁干净,减少了歧义;移除了很多 C 风格语法使代码风格更加一致,可读性更高。总体上说,Swift 3 更加优秀。我们来看看 从语言使用的角度上,Swift 3 到底有哪些改变和新特性。
先来看看可能是改动行数最多的一个变化:API 的重命名和规范。新的 API 设计强调了 API 调用 时的清晰,因为方法和属性只会声明一次,但反复调用。在保持语义、避免歧义的前提下,尽量简洁,减少冗余。
比如:
方法名中应该要包含所有需要的单词以减少阅读时的困惑,比如从一个 List 中移除指定位置的一个元素:
extension List { public mutating func remove(at position: Index) -> Element } employees.remove(at: x)
如果省略方法名中的 at,读代码的时候就不明确到底是删掉 x,还是删掉所在的位置 x 的元素。
如原来的 CollectionType 变成 Collection;一些特殊的情况下,加上 Protocol 后缀避免歧义,如原来的 ErrorType 变为 ErrorProtocol。
如:
Sequence.minElement() => Sequence.min()
UIColor.blackColor() => UIColor.black
Mutating 方法的命名应该是一个动词,而 non-mutating 应该用 ed/ing 形式的词。如 array.sort() 是对 array 本身进行排序,而 array.sorted() 则是返回一个新的 Array。
很多方法的名字都大大缩短,比如原来 stringByAppendingString(aString: String)变成 appending(_ aString: String)。
还有很多条目这里不一一列举,感兴趣的可以官方的 Design Guidelines
很多 Objective-C Constants 在 Swift 里成了 Enum。比如 HKQuantityTypeIdentifier:
enum HKQuantityTypeIdentifier : String { case bodyMassIndex case bodyFatPercentage case height case bodyMass case leanBodyMass }
这里顺便提一下,Swift 3 里的 Enum 都是小写字母开头。
C 的 APIs 原来都是会全局的方法和变量,现在都引入为成员变量使用。如大家熟悉的 CGContext 在 Swift 3 里:
context.lineWidth = 1 context.strokeColor = CGColor(gray: 0.5, alpha: 1.0) context.drawPath(mode: .Stroke)同样 Dispatch 的 APIs 也更加 Swifty:
let queue = DispatchQueue(label: "com.example.imagetransform") queue.async { let smallImage = image.resize(to: rect) DispatchQueue.main.async { imageView.image = smallImage } } let delay = DispatchTime.now() + .seconds(60) queue.after(when: delay) { // ... }
自增 ++ 跟 自减 -- 运算符被移除了。
C 风格的 for 循环(for (int i = 0; i < array.count; i++) )也不能使用了。
Enum 有不少改动,最明显的改动是所有的成员都变成了小写。其次,在枚举当中,必须要在成员前加上左前缀点。之前只是在枚举外使用需要加,现在枚举内部对 self 进行switch,也一定要加,确保代码的一致性。
enum Season { case spring case summer case fall case winter var foo: String { switch self { case .spring: return ... case .summer: return ... ... } } }另一个改动是,有关联值的 enum,在 case 后可以声明多个成员:
enum MyEnum { case case1(Int, Float) case case2(Float, Int) } switch value { case let .case(x, 2), let .case2(2, x): print(x) case .case1, .case2: break }
在 Swift 3 之前,我们有三种访问级别:
默认的是 internal,表示这个成员只能在 module 内可见。如果要在 module 外使用,就要将其设置为 public。private 表示该私有成员只能够在其所在文件内可用。
在 Swift 3 中,多了一种访问级别 fileprivate。fileprivate 的效果相同于之前的 private,而 private 表示只在其对应的作用域中可见。这让访问级别更加清晰。
在 generic 声明中,where 语句被移到了最后。Swift 3 之前,我们可能这么声明一个 generic 的方法:
func anyCommonElements<T : SequenceType, U : SequenceType where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element>(lhs: T, _ rhs: U) -> Bool { ... }Swift 3 中,正确的语法应该是:
func anyCommonElements<T : SequenceType, U : SequenceType>(lhs: T, _ rhs: U) -> Bool where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element { ... }
where 在条件判断语句中被删掉,取而代之的是逗号。原来这样的一个 guard 判断:
guard let x = opt1, y = opt2 where x == y else {}在 Swift 3 中应该写成:
guard let x = opt1, y = opt2, x == y else {}
原来隐式解析 Optional 类型没有了,但其语法还在。
let x: Int! = 5 let y = x let z = x + 0
x 的声明后面有个 !,看起来像个 IUO,但其实是一个 optional 类型。x 赋值给 y,y 是 Int?,因为这里不需要进行强制的解析。但 z 是一个 Int,因为这里需要解析 x 里的值才能进行加法运算。
在我进行 Swift 3 升级的过程中,遇到不少坑,有的很难测试出来,甚至导致我们需要重新提交一个新的版本。
首先,Enum 的成员从大写变成小写。乍看起来没什么问题,但如果你的代码中有用到 String Raw 类型的 Enum,并且存储了它的 rawValue 的话,你代码升级后再把值取出来可能就有问题。比如 Season Enum 的例子:如果原来你把 Season.Spring.rawValue 存起来,比如 User Defaults 里。现在你再拿出来 Season(rawValue: {springRawValue}),会得到一个 nil,因为新的 rawValue 都是小写。
另外一个是 Json Dictionary 声明的变化,原来 Json Dictionary 的类型通常是 [String: AnyObject]。而在 Swift 3,则是用 [String: Any]。Any 包括 optional 类型,而当你要是用 NSJSONSerialization.dataWithJSONObject 对含有 Optional 类型的 Dictionary 序列化的话,就会有 runtime error 抛出。
以上是印象深刻的两个问题,其他问题 code 不同,可能会有不一样的表现,这里也不一一举例。
升级 Swift 3 的过程虽然痛苦,也难以避免伴随着不少 bug 的出现,但熬过这个之后,Code 看起来写起来都会更加畅快。之后的 Swift 版本不大可能再出现这种规模的迁移。Swift 4 我们可能回看到完整泛型,并发,以及更多有趣,强大的特性。期待。
原文:http://tech.glowing.com/cn/swift3/