Swift3迁移指南-2
2016年07月21日 Swift

使用Carthage/CocoaPods工程

如果你使用了第三方的二进制形式的Swift模块工程,这个工程并不是在你自己Xcode的工作空间中由你自己生成出来的,你可以选择下面的其中一种方法进行迁移。

  • 在你的Xcode工作空间包含工程的源代码。

    使用这种方法,你需要构建并迁移开源的工程到你自己的工程里面,使用Xcode7.3确保工程构建和链接都无误。包含开源工程中的文件到你自己的Xcode工作空间里,并设置好你正在构建的工程的依赖工程scheme。如果你在Carthage的build目录下设置了framework的Swift的二进制模块的搜索路径,去掉设置的搜索路径或者直接清掉Carthage的build目录,要确保你所使用的Swift模块一定是由你自己的Xcode工作空间构建出来的。

  • 一直等到你所使用的开源工程也更新到了Swift2.3或者Swift3

    可以按下面的步骤来迁移你的工程

    • 把你的工程当做是在Xcode7.3下构建
    • 使用迁移助手,且只应用迁移助手对你自己工程的源代码改动部分。
    • 构建之前,修改Carthage/CocoaPods依赖文件,指定其为已经迁移到Swift2.3或者Swift3的工程分支版本
    • 更新依赖,并尝试构建已经被迁移程序修改更新过源代码的工程。

已知的迁移问题

Swift 标准库

  • 迁移程序可能会对在SetIndexDictionaryIndex使用索引方法的这种代码转换失败。

    • 解决方法:手动迁移用在集合对象上的索引方法。大致如下:
      • index.successor() 修改为 Collection.index(after: index)
      • index.predecessor() 修改为 Collection.index(before: index)
      • index.advancedBy(delta) 修改为Collection.index(index, offsetBy: delta)
      • index.advancedBy(delta, limit: otherIndex) 修改为Collection.index(index, offsetBy: delta, limitedBy: otherIndex)
      • index.distanceTo(otherIndex) 修改为Collection.distance(from: index, to: otherIndex)
  • 在Swift2.2中Unmanaged类型有一个静态方法fromOpaque(_:)COpaquePointer类型转换为Unmanaged类型和一个实例方法toOpaque()把未托管的引用类型转换为COpaquePointer类型 。在Swift3中这个变成了把UnsafePointer<Void>转换为UnsafeMutablePointer<Void>来匹配一个通用的传递给C的API的“上下文指针”,你可以简单的把用到COpaquePointer的移除即可(现在已重命名为OpaquePointer)。

  • 如果你有任何的的自定义的集合类型,你可能会看到如下编译错误:“MyCollection’ does not conform to protocol ‘Collection”(MyCollection没有遵循Collection协议)。

    • 现在是由集合类型负责其自身的索引的自增/自减,要让你自己的集合类型遵循Collection协议,实现func index(after: Index) -> Index方法,对于BidirectionalCollection这个集合类型,还要实现func index(before: Index) -> Index方法。
  • 如果你有一个使用了半开区间操作符生成的Range类型的变量(如 1..<2)并且把它做一个Sequence来用(比如,用在一个for-in循环里),你会看到如下错误:“type ‘Range’ does not conform to protocol ‘Sequence”(Range类型没有遵循Sequence协议)。

    • 修改方式就是把Range改为CountbaleRange
  • 用户可能需要手动修改 Sequence.generate()Sequence.makeIterator().

  • 用户可能需要手动修改 anyGeneratorAnyIterator.

  • 用户可能需要手动修改 Range.startRange.end 分别改为Range.lowerBoundRange.upperBound

  • 用户可能需要手动修改 Collection.Index.DistanceCollection.IndexDistance (没有中间的点号)

  • 访问Collection.enumerated()的枚举结果时,用户可能需要手动修改元组元素 indexoffset

  • PrintableDebugPrintable 现在已分别重命名为 CustomStringConvertibleCustomDebugStringConvertible

  • 如果在迁移索引区间之后使用集合的indices属性的时候报“Range<Index>没有遵循Sequence协议”的错。像下面这样修改:

    • for _ in str.startIndex.. –> for _ in str.indices[str.startIndex..<someIndex]{}
  • Zip2Sequence(_:_:) 构造器已被移除; 使用 zip(_:_:)函数替代。

  • Collection扩展中使用 min/max 会与Collection的本地方法冲突; 在 min/max 之前加swift就可以解决这个冲突。

  • 在迁移程序会自动重命名代码中的类型名,新的类型名可能会与用户自定义的类型名冲突,解决这个问题需要你移除那些不必须的类型别名定义。

  • Selector() 会被转换为 nil

  • Unmanaged.toOpaque() 会被转换为 OpaquePointer(bitPattern:)

  • Range<>.reversed 已经被移除; 用户可以调用[].indices.reversed()模拟实现这个功能。

  • <Index> ..< <Sequence>.endIndex 需要手动修改为<Sequence>.indices.suffix(from: )

  • 迁移程序不会重写Swift中不存在的类型的泛型约束。

    比如:

    func foo() <C: CollectionType where C.Index: BidirectionalIndexType>{} 会被转换为 func foo()<C: BidirectionalCollection> {} 但其实应该转换为这样func foo()<C: Collection where C.Index: BidirectionalIndex> {}

SDK

  • 新SDK的release版本一些协议增加了一些required的方法(遵循协议必须要实现的方法),迁移程序现在不会在你的源代码里加入这些方法(required方法)实现。

    • 解决办法就是手动在遵循这些协议的代码里添加required方法的实现。
  • 在Swift3中很多Foundation中的“字符串类型”的API已经被改为使用结体的“包装类型”。比如新的Notification.name

  • FileAttributeKey是另一个由“字符串类型”改为使用结构体的“包装类型”,当像这样的类型被用在字典(比如FileManager中的 attributesOfItem(atPath:)方法的返回结果)上的时候,字符值通常需要使用rawValue属性提取出来。

    • let mtime = try FileManager.default().attributesOfItem(atPath: "/")[FileAttributeKey.size.rawValue] as? NSNumber
  • 迁移程序会修改绝大部分用到NSURL的地方为URL,但是NSURL中的某些方法,像checkResourceIsReachableAndReturnError,没有使用Swift的错误处理机制,而是使用是外部参数来保存错误信息。而某些URL里类似的方法,如checkResourceIsReachable,如期的使用了Swift的错误处理机制。

    • Swift3的迁移程序依然迁移时保留了NSURL方法,如果你想使用新的API里的URL类型,需要手动修改自己的代码。通用的处理url不可达的错误方式是使用try?:

      let isReachable = (try? resourceURL.checkResourceIsReachable()) ?? false)

    • NSURLport属性是一个可选的NSNumber类型,同时对应在新的URL类型是一个可选的Int类型。Swift3的迁移程序迁移时保留了NSURL的属性,如果想使用新的URL类型需要手动修改代码为新的API。

  • 迁移程序会把大部分的NSData转换为Data,某些NSData中的方法操作UnsafeMutablePointer<Void>,相对于Data里面的方法使用UnsafeMutablePointer<UInt8>(相对于Data.copyBytes(_:length:)方法,NSData.getBytes(_:length:) 方法更容易接受)。需要注意,Swift类型的内存布局是不能保证的。

    • 迁移程序会依然保留使用NSData的方法;想用新API需要自己手动修改代码。
    • NSData(contentsOfMappedFile: x)可被修改为Data(contentsOf: x, options: .mappedAlways)
    • NSData(data: x) 可以被修改为 x
  • 在Swift3中除非有更好的方式来代替NSDate,否则迁移程序都会保留使用NSDate,如:

    • (x as NSDate).earlierDate(y) 可以改为 x < y ? x : y
    • (x as NSDate).laterDate(y) 可以改为 x < y ? y : x
  • 迁移程序可能不能正确的处理NSEventSubtype 枚举。

  • 迁移程序可能不能正确处理属性的setter方法,导致出现这种:setInstanceVariable = instanceVariable

    • 解决办法:修改setter为属性名字,如:self.instanceVariable = instanceVariable
  • 在新SDK中会有switch语句中新增了一些case语句,但迁移程序不会在你的代码里做处理。

    • 解决办法:手动加入新的switch语句中的case,并加入合适的可用性判定
  • 错误:由CALayer?向下转换为CALayer只能可选解包;你是想使用’!’?

    • 去掉as! CALayer,用!代替
  • 错误:枚举值tv不存在于类型UIUserInterfaceIdiom

    • 改为TV
  • 迁移程序可能会错误的处理像objc_ASSOCIATION_RETAIN这种小写的枚举值。

    • 解决方法:把它们全部改为大写OBJC_ASSOCIATION_RETAIN
  • 迁移程序会转换全局常量为名称空间枚举,但可能并不会赋一个合适的.rawValue值,传入一个接受一个枚举值的函数时使用新的枚举类型。

  • 迁移程序不会转换NSNumber.unsignedLongValue

    • 解决办法:手动改为.uintValue
  • 迁移程序可能不会将XCUIElementQuery.elementBound(by:) 转换为XCUIElementQuery.element(boundBy:)

  • 一些类型现在已变成泛型(如:NSCache -> Cache, NSMapTable ->MapTable),迁移到Swift3之后需要为它们加一个合适的泛型参数。

  • 迁移程序可能不能正确的将 NSApplicationDelegate类的applicationShouldTerminateAfterLastWindowClosed(_:) 方法转换为applicationShouldTerminate(afterLastWindowClosed:), 这会引发一个警告: “Instance method ‘…’ nearly matches optional requirement ‘…’ of protocol NSApplicationDelegate”。

    • 解决办法:将方法名改回applicationShouldTerminateAfterLastWindowClosed
  • 如果你在一个子类遵循一个Objective-C协议,并实现了其可选的的方法,你会看到这样一个警告:“Instance method ‘…’ nearly matches optional requirement ‘…’ of protocol ‘…’”

    • 解决方法:在实现的可选方法之前添加@objc(objectiveC:name:)属性。
  • 使用字面量做为选项现在需要调用对应选项的构造器,如:NSWindowStyleMask(rawValue: 8345)

  • 迁移程序不会修改类似这种有相同值类型的NSMutable*(如:NSMutableData -> Data, NSMutableURLSession -> URLSession),但绝大部分SDK的函数现在接收新的值类型。

    • 修改为与它们的值类型等同的,小心处理引用与值的转换。快速的解决办法是直接把它们转换为需要的类型(如 as Data),但是这可能会造成额外的拷贝。
  • 如果你定义过的一个泛型与现在某个Foundaiton类冲突。迁移程序可能会在这个类用于一个不同的目标的时候错误的移除掉泛型参数。

    • 如果你定义的类与Foundation有冲突,首先重命名类以防止产生冲突,或者回退那些被去掉的泛型参数。
  • 迁移到Swift3之后,你会看到类似这样的错误:“Extension of a generic Objective-C class cannot access the class’s generic parameters at runtime”

    • 当尝试调用一个Objective-C中泛型类中含有泛型参数的方法时,可以通过调一个API产生去掉self属性的变量来解决这个问题。如:let typeErasedSelf = self as! MyObjCType<AnyObject>
  • 如果代码中调用了self.init(...),这时在迁移到Swift过程中就会错误的将self.init转换为self.dynamicType.init,这时会导致这样的错误:“return from initializer without initializing all stored properties”。

    • 解决方法:去掉.dynamicType
  • 迁移程序在转换类似这样的函数时Pasteboard.readObjects(forClasses:options:),会强制重命名第一个参数,比如把NSURL.self改为URL.self,这样就会导致一个编译错误;解决这个问题就是去掉迁移程序做出的改动。

  • 迁移程序在转换NSData(bytes:length:deallocator:)时,并不会修改析构器的类型。

    • 解决办法:将(UnsafeMutablePointer<Void>, Int) -> Void修改为(UnsafeMutablePointer<Int8>, Int) -> Void
  • 某些方法会标记为在watchOS中不可用,但是对于iOS仍然是必须的,如果出现了这样不能重写某些不可用的方法的错误,请使用#if os(iOS)块将对应的代码包起来。

  • 用户可能需要手动将String(contentsOfURL:usedEncoding:)改为String(contentsOf:usedEncoding:),现在这个方法在usedEncoding参数上用inout String.Encoding代替了UnsafeMutablePointer

  • 有必要手动将NSBundle.url(orResource:withExtension:)修改为Bundle.urlForResource(_ :withExtension:subdirectory:inBundleWith:)

  • 在迁移程序自动迁移之后一些值类型由NSURL变为URL,导致出现不可用成员的错误。解决这个问题,需要手动添加类型转换,用URL的例子,就像这样x as NSURL

  • 用户可能需要通过类型推断来手动简化选项设置。如:修改DispatchQueue.global(attributes: DispatchQueue.GlobalAttributes.qosDefault)DispatchQueue.global(attributes: .qosDefault)

  • 尾闭包可能由于名字变得更短而产生歧义,可以通过在闭包参数加参数标签来解决。

    比如:

    1
    2
    let parameterTree: AUParameterTree
    parameterObserverToken = parameterTree.tokenByAddingParameterObserver {...}

    变为

    1
    parameterObserverToken = parameterTree.token {...}

    歧义是由于其他的tocken(:)方法,修改为加入参数标签

    1
    parameterObserverToken = parameterTree.token(byAddingParameterObserver: {...})
  • Dispatch

    • dispatch_once函数在Swift中不再可用。在Swift中你可以使用延迟初始化全局或者静态属性来达到由dispathc_once提供的确保只调用一次的功能。例如:

      1
      2
      let myGlobal = { … global contains initialization in a call to a closure … }()
      _ = myGlobal // using myGlobal will invoke the initialization code only the first time it is used.
    • 迁移程序可能不能正确处理多个使用相同的token在不同的静态变量中调用dispatch_once的情况。

    • 调用dispatch_after可能会导致被迁移转换为错误的参数标签:如果你看到这样的错误

      “Argument labels ‘(when:, block:)’ do not match any available overloads” ,把block改为execute即可修复。

    • 现在针对每个DispatchSource类型都有特定的协议。你需要修改dispatch_source_t 为其中一个特定的协议,比如DispatchSourceTimer, DispatchSourceProcess,等合适的。

    • Dispatch 队列API现在使用这个 DispatchAttributes OptionSet。如果你之前使用

      dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0)),现在你使用DispatchQueue(label: name, attributes: [.serial, .qosDefault])这里的选项设置。

    • 迁移程序会错误的把dispatch_get_global_queue(_:_:) 转换为DispatchQueue.global(_:_:)来代替DispatchQueue.global(attributes:)

      • 解决方法:移除第二个参数,确保第一个参数是DispatchQueue.GlobalAttributes的枚举值。
    • dispatch_get_specific 不再带一个UnsafeMutablePointer参数,并且现在它不会加上必需的参数标签。

      • 解决方法:替换UnsafeMutablePointer<Void>的key为DispatchSpecificKey<T>,同时加上缺少的key标签。
    • dispatch_block_t 不会自动转换为(Void) -> Void

    • dispatch_qos_class_t 不再是必需的;用户可以使用Swift的选项设置语法设置DispatchQueueAttributes 成员为自己想要的配置。

Swift 3语言细节

  • 迁移程序不会转换rand()用法,这个函数现在不能用了。需要使用arc4random()

    • 解决方法:将所有的rand()用法都改成arc4random()arc4random_uniform(),谨记arc4random()返回一个UInt32而不是Int32
  • 迁移程序不能完全转换接受ImplicitlyUnwrappedOptional类型参数的闭包。

    • 解决方法:提升为使用标准可选类型。
  • 迁移程序可能错误的在需要!的可选类型隐式解包的值的后面插入?,这允许空值静默传播代替确定性陷阱。

    • 使用!来代替?
  • 迁移程序不转换不再返回可选值的if let语句。

    • 解决方法:在if let移除对应的语句,如果你需要一个词法定界,可以它对应的语句放到do语句中。
  • 迁移程序不会添加为枚举值添加前置句点。当迁移程序把枚举值都变为小写时会造成冲突。

    • 解决方法:为没有前置句点的枚举值手动添加句点
  • 去掉NS前缀的Foundaiton中的类型冲突的自定义属性冲突,会自动添加模块标识的类型名字。例如:如果有这样的定义var URL:NSURL,它会被重写为var URL: Foundation.URL

    • 解决方法:在进行代码迁移之前把这样的属性重命名,这样就会冲突了。Swift的API设计指南建议这样的应该是小写的。
  • 迁移程序不会处理C形式的递减的for循环,如:for var i = right; i > left; i-- {}

    • 解决方法:手动修改循环为for-in形式的或者while形式的循环。
  • 迁移程序可能错误的在自定义的闭包类型添加括号。如:从inout State -> Element?变为inout (State) -> Element?,正确的修改应该是(inout State) -> Element?

  • 迁移程序会给SequenceType添加不必要的Swift模块标识符,如:

    struct MySequence: SequenceType =>struct MySequence: Swift.Sequence

    • 解决办法:移除前置的Swift
  • 迁移程序可能会略掉小写之后的枚举值。

    • 解决方法:手动处理一下迁移程序忽略掉的小写的枚举值。

Swift 2.3

  • 迁移程序在迁移到Swift2.3的过程中可能会错误的转换Range<T>CountableRange<T>
    • 解决办法:改回Range<T>,Swift2.3没有CountableRange<T>,只有Swift3有。
    • 迁移程序会为使用了dispatch_queue_set_specific()添加一行这样的/*Migrator FIXME: Use a variable of type DispatchSpecificKey*/注释,这个只在Swift3中有用,如果是迁移到Swift2.3,可以忽略这个。

其他杂项

  • 如果在你的工程里有多个scheme包含了不同的target,你只需要关注你要迁移的那个。你应该手动选项一个新的scheme。此时运行Edit->Convert->To Current Swift Syntax迁移其他的scheme,或者创建一个scheme包括工程内所有的target,并且保证在运行迁移助手工具的时候已选中这个scheme。