Swift3迁移指南-1
2016年07月18日 Swift

前记

Swift3开发者预览版已经开放了,3.0release估计很多就来了,Swift 3相对于Swift2.x变动非常大,看官方有一个Swift 3的官方迁移指南,这里粗略翻译了一下,也算是自己熟悉一下。分成二部分翻译,第一部分就是迁移程序的各种注意事项,第二部分是各种细节问题解决方案。后续把swift-evolution的语法/特性变动列表翻译一下。

Xcode8.0带来了一个迁移工具,能帮你将现在的Swift工程迁移到Swift3,也能让你的工程更新到Swift2.3和新的SDK版本。

预迁移准备

为了达到最好的迁移效果,确保你想要迁移的工程能在Xcode7.3下构建成功,并通过了所有测试。

同时还要保证工程在版本管理之下,这能让你看到在通过迁移助手迁移完成之后工程内的变化,并且在有需要的时候可以随时回退变化,再次去尝试迁移。

假如有多个scheme用来构建不同的独立product(或者对应不同平台下相同的product),那么在你的工程里创建一个为你需要的所有平台构建所有东西的scheme还是相当重要的,包括你用于的单元测试的target。迁移助手会根据你选择的scheme的更改来生成,所以所有需要处理的target都要一起到包含到这个scheme里。

要检查或者修改已经被包含到scheme里的target,选中“Edit Scheme”选项,在左侧切换到“Build”标签,确认你所有的target和其单元测试都已经被包含进scheme里了。

如果你的工程依赖由Carthage或者CocoaPods提供的第三方开源的工程,参考使用Carthage/CocoaPods工程这节。

Swift迁移助手

当你首次用Xcode8.0打开工程的时候,会有弹窗提示你通过迁移助手来做一个迁移测试。这个工具也可以通过菜单“Edit->Convert->To Current Swift Syntax…”手动打开。

你可以执行下面二种迁移:

  • 使用Swift2.3 修改你的工程开启使用旧式Swift的构建设置,并提供代码更改来确保可以在新的SDK下使用。
  • 使用Swift 3 推荐使用这种。你的工程得到代码的更改且能通过Swift3.0来构建,同时能使用Xcode8.0里面的所有新功能。

你还可以选择先将工程通过迁移助手迁移到Swift2.3,之后再迁移更新到Swift3.0。

在你使用迁移助手选择“使用Swift2.3”或者“使用Swift3.0”之后,你得到一个将要被迁移的target的列表。此时这些target不包含任何Swift代码并且也不能被选中。有一个已知的问题,影响那些不包含Swift文件但是“Emned Swift Standard Libraries”设置却可用的target,原本这个设置应该被勾选的,但是却没有。安全起见,把所有的列表中target都选上。

点击“Next”,会来到“Generate Preview(生成预览)”页,助手即将开始启动迁移来生成代码的更改。当这些都完成之后,在点击“Save”之后,你会看到所有这些变动都会生效。注意这点, 在比较的界面中,原始的代码(转换之前的)在右边,变动之后的在左边。点击“Save”之后,这些代码变动就会应用到原始文件里。如果你选择的是迁移到Swift2.3,所有这些target都会拥有一个“使用旧式Swift”的构建设置集合。

在处理target的时候可能会出现影响迁移处理的问题。切换到“Report Navigator”选择“Convert”入口;这是一个转换的生成日志。检查日志里显示的错误,如果你发现错误是从你自己的一个target中引入Swift模块失败了(比如,“No Such module ‘NAME’ ,这里显示的‘NAME’是其中一个target的名字”),这可能是你遇到了一个已知的问题,就是迁移工具生成时没有正确的处理隐式的依赖模块(也就是这个叫‘NAME’的target没有在处理依赖它的target的之前处理)。解决办法,编辑scheme在‘Build’标签里显式的加入这个target(点击左下的加号按钮,在已选择的target列表中找到这个target),在列表中,如果依赖它的target在它前面。“Parallelize Build”项必须不能勾选。

如果看到的错误是关于不能签名target,试着在这个target的构建设置禁用签名。

如果看到其他的错误,请在https://bugreport.apple.com详细描述并反馈。

如果你需要使用其他任何的替代方法,回退之前所有通过迁移助手产生的变动,使用替代方法,再手动用迁移助手从头开始转换一次。

Swift 3 迁移变动概览

Swift 3有非常多的重大变动,迁移工具会帮你。你可以通过下面的链接大致看一下Swift 3的改进提案:

https://github.com/apple/swift-evolution](https://github.com/apple/swift-evolution

下面是一份简要的源码崩坏修复的概述:

API设计指南

Objective-C的API已经依照Swift的API设计指南来引入。这会同时影响如何引入SDK和用户自己的Objective-C的frameworks。Swift标准库也做了很多改动来遵循设计指南。更多细节请参考提案SE-0005 - Better Translation of Objective-C APIs Into Swift.

迁移程序会把用户自己定义的枚举都变成小写以此来让它们与新的设计指南相匹配。

SDK

像CoreGraphics,Dispatch,Foundation里的其他类型这些主要的framework,不再做为一组全局函数和变量来引入了,改为做为Swift类型各自的方法和属性来引入。更多细节请看提案SE-0044 - Import as member, SE-0088 - Modernize libdispatch for Swift 3 naming conventions.

Swift 3里在主要的基础类型中,‘NS’前缀已经被移除了。请看SE-0086 - Drop NS Prefix in Swift Foundation.

Swift 标准库

Swift 3中集合类型的索引模型被改动的相当大,更多细节在此SE-0065 - A New Model for Collections and Indices

最明显的改就是不再有像successor()predecessor()advancedBy(_:)advancedBy(_:limit:),或者distanceTo(_:)这些索引方法了。这些操作都被移到用于递增和递减切片集合中了。

myIndex.successor() => myCollection.index(after: myIndex)
myIndex.predecessor() => myCollection.index(before: myIndex)
myIndex.advance(by: …) => myCollection.index(myIndex, offsetBy: …)

如果迁移程序不能检测到集合是用于切片,就会插入一个编辑器占位符,这时就需要自己必须填入正确的集合了。

为了支持集合的更改,Range类型也做了一些变动。之前的x..<yx...y产生相同的Range类型。现在这些表达式能产生四种类型:RangeCountableRangeClosedRangeCountableClosedRange。我们将Range拆分为RangeClosedRange类型允许包含类型最大值的闭区间(如,0...Int8.max是可以的)。普通的区间类型和它们的~Countable变体在功能上的不同:

  • Range<Bound>ClosedRange<Bound>现在只需要Comparable做为边界。这意味你可创建一个Range<String>
  • RangeClosedRange不能被遍历(它们不再是集合类型),因为值仅是一个不能被递增的Comparable
  • CountableRangeCountableClosedRange需要Strideble,并且它们符合Collection,所以可以遍历它们。

..<...操作符试着做正确的事情并返回最符合的区间,所以像这样的代码for i in 1..<10推导出一个CountableRange类型,代码还是可以正常工作的。假如你有一个做为一个区间类型的变量,并且你想把它传给一个接收不同类型的区间类型的API,这时需要在区间类型变量上使用构造器来转换一下:

1
2
var r = 0..<10 // CountableRange<Int>
Range(r) // converts to Range<Int>

语言

  • 函数第一个参数标签一致化

    现在是考虑函数默认要有第一个参数标签,请看SE-0046 - Establish consistent label behavior across all parameters including first labels。迁移程序会添加下划线标签,以保留已经存在API:

    1
    func foo(bar: Int) => func foo(_ bar: Int)
  • 对UnsafePointer的处理变动

    在Swift 3中,现在是使用可选类型来表示可为空的空对象指针类型,如UnsafePointer<Int>?,请看 SE-0055 - Make unsafe pointer nullability explicit using Optional。这表示UnsafePointerUnsafeMutablePointerAutoreleasingUnsafeMutablePointerOpaquePointerSelector, 和 NSZone现在表示非可空指针,也就是这些指针永不为nil。使用这些类型的代码可能需要做这几项更改:

    • 要设置指针为nil,类型必须是可选的。迁移程序会处理几种简单的情况,但是通常你必须确定你的指针是仅仅如对象引用一样可以为可选类型。

    • 从C函数获取的返回的可为空的结果在访问pointee属性(原来的memory)或者下标元素之前必须显式解包。这里链式可选语法还是可以继续使用的。如:

      result?.pointee = sum

    • 接收或者返回指针类型的回调(C函数或者blocks)必须与使用或者忽略Opional类型的原始定义相一致。

    • 由于编译器的限制,不允许传递指针给使用了C可变参数的函数(如NSLog)。替代方法是使用下面这种形式用一个指针大小的整型来代替,做为参数传递过去:

      Int(bitPattern: nullablePointer)

  • 现在Objective-C轻量泛型类型做为泛型类型引入

    SE-0057 - Importing Objective-C Lightweight Generics

    因为Objective-C的泛型不会在运行时表现,所以在Siwft中使用他们有一些限制:

    • 如果Objective-C的泛型类被用于as?as!,或者is转换中,这个泛型的参数不会在运行时被检查。如果转换成功的操作数是一个Objective-C的类实例,那参数就会忽略。

      1
      2
      3
      let x = NSFoo<NSNumber>(value: NSNumber(integer: 0))
      let y: AnyObject = x
      let z = y as! NSFoo<NSString> // Succeeds
    • Swift 子类只能继承一个参数类型被完全指定的Objective-C泛型类。

      1
      2
      3
      4
      // Error: Can't inherit Objective-C generic class with unbound parameter T
      class SwiftFoo1<T>: NSFoo<T> { }
      // OK: Can inherit Objective-C generic class with specific parameters
      class SwiftFoo2<T>: NSFoo<NSString> { }
    • Swift可以扩展Objective-C泛型类,但是扩展不能含有约束,并且扩展内定义不能访问类的泛型参数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      extension NSFoo {
      // Error: Can't access generic param T
      func foo() -> T {
      return T()
      }
      }

      // Error: extension can't be constrained
      extension NSFoo where T: NSString {
      }
    • 基本容器类NS[Mutable]ArrayNS[Mutable]Set,和NS[Mutable]Dictionary现在仍然做为非泛型类型引入。

迁移之后

虽然迁移程序会替你处理那些固定的变动,但还是可能需要你进行大量的手动修改以保证在迁移程序做出迁移改动之后能成功构建出项目。

你可能会看到一些已经关联到的fixit错误;虽然迁移程序是用来消除Swift 3编译器提供的fixit的,但是一个已知的限制就是迁移程序不能保证100%的没问题(特别是在二个target之间有内部之间的依赖的的时候),可能会有些fixit会被忽略掉。

即使编译没问题,迁移程序生成的代码也不是完美的,比如,你可能会看到转换为带‘NS’前缀的类型(url as NSURL),这样的代码显然是使用相关API重构为新的URL类型更为合适。你会看到迁移程序会在需要转换为更合适的代码的地方加入了新的注释(/*Migrator FIXME: ...*/)。

查看Known Migration Issues这节,是一个你在迁移工程的过程中可能会遇到的问题的列表。