使用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 标准库
迁移程序可能会对在
SetIndex和DictionaryIndex使用索引方法的这种代码转换失败。- 解决方法:手动迁移用在集合对象上的索引方法。大致如下:
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().用户可能需要手动修改
anyGenerator为AnyIterator.用户可能需要手动修改
Range.start和Range.end分别改为Range.lowerBound和Range.upperBound。用户可能需要手动修改
Collection.Index.Distance为Collection.IndexDistance(没有中间的点号)访问
Collection.enumerated()的枚举结果时,用户可能需要手动修改元组元素index为offset。Printable和DebugPrintable现在已分别重命名为CustomStringConvertible和CustomDebugStringConvertible。如果在迁移索引区间之后使用集合的
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.nameFileAttributeKey是另一个由“字符串类型”改为使用结构体的“包装类型”,当像这样的类型被用在字典(比如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)在
NSURL中port属性是一个可选的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>
- 当尝试调用一个Objective-C中泛型类中含有泛型参数的方法时,可以通过调一个API产生去掉self属性的变量来解决这个问题。如:
如果代码中调用了
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
2let parameterTree: AUParameterTree
parameterObserverToken = parameterTree.tokenByAddingParameterObserver {...}变为
1
parameterObserverToken = parameterTree.token {...}
歧义是由于其他的tocken(:)方法,修改为加入参数标签
1
parameterObserverToken = parameterTree.token(byAddingParameterObserver: {...})
Dispatch
dispatch_once函数在Swift中不再可用。在Swift中你可以使用延迟初始化全局或者静态属性来达到由dispathc_once提供的确保只调用一次的功能。例如:1
2let 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现在使用这个
DispatchAttributesOptionSet。如果你之前使用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。