Swift 4.1 迁移指南

Posted 小得写代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 4.1 迁移指南相关的知识,希望对你有一定的参考价值。

*代码建议横屏阅读


理论上来讲,升级到一个小版本的语言和SDK的更新,应该是个比较顺滑的过程。然而这次Swift 4.1 / Xcode 9.3 的升级所带来的工作超出了预期。下面分『通过编译』、『通过测试』以及『去除警告』三个步骤来说。

1. 通过编译

相信这次 Swift 4.1 的升级对于有一定代码量并且引用第三方库源码编译的项目来说,需要处理的事情还不少,通过编译就没那么容易,先看下面的代码

aView.y = bounds.height + margin - (102+20+20+36)/2.0

编译居然出错了:

Expression was too complex to be solved in reasonable time; consider breaking up the expression

看到这个错误时,笔者真的笑了又笑、笑出了声。首先这个表达式没那么复杂,居然编译器认为在合理时间中编不过了。但同时,我又表示了充分的理解,因为Swift 4.1之前版本中,对于这种『复杂』的表达式编译的速度是非常慢的。修正方式:拆一个局部变量出来,写成两段。

这个编译错误的出现,意味着 Swift 4.1 中,编译速度得到了监控。我亲测了一下,我们整个项目编译时间缩短了30%以上,之前复杂类型推断的方法需要编译几分钟,这个问题在 Swift 4.1 中得到了修复。

我们来看一下第二个编译不过

func asyncDoSth(_ completion: ((Void) -> Void)?) {
 // compile error    
  completion?()
}    

func asyncDoSth(_ completion: (() -> Void)?) {    
  completion?()
}

我们首先来复习一下,Void 实质上是一个空的tuple,即(),所以第一个方法如果需要编译过的话,completion?( () ),可以传入一个空 tuple。对于一般的应用场景来说,其实不太会碰到这个编译不过的问题,因为第二种写法是我们的通常写法。典型的问题场景一般与泛型框架有关。

第三个编译不过不是 Swift 相关了,而是 Objective-C 的 格式化相关的函数报编译错误了,我们使用了 IGListKit 框架,这个框架正好踩中了这个坑,官方修复速度不足够快,那自己上了。问题的本质是 NSInteger 这样的类型在32位和64位机器上的 size 是不一样的,因此一个安全的格式化的方式是统一向上转型,然后用%ld输出。

2. 通过测试

编译过只是第一步,新的 SDK 和 语言的升级也会带来新的问题。这时候经过简单自测后,需要请测试同学帮忙全量回归测试了。不测不知道,一测吓一跳。

第一个P0 Bug,分享跳转回来都挂了!

func application(_ application: UIApplication, open url: URL, 
sourceApplication: String?, annotation: Any) -> Bool {

由于一些众所周知的原因,我们还需要 hook 这个已经废弃的方法来实现应用间跳转的流程,可是,分享回来之后,在任何函数被调用之前,居然挂了。

经过一番折腾发现,似乎是 nil 不能被转换成 Any,WTF? 权宜之计,在 annotation 的 Any 之后加一个问号吧。笔者测试了 Xcode 9.4 Beta,发现在 Swift 4.1.1 中这个 Bug 被修复,属于 Swift  标准库的问题。

第二个Bug,隐藏得就更深了,与Swift 内部实现机制有关(同时访问)

protocol Z {
    var x: Int {get set}
    var y: Int {get set}
}

struct ZZZ : Z {
    var x : Int
    var y : Int
}

class A {
    var p : Z = ZZZ(x: 2, y: 3)
    func f() {
        p.y = max(p.x, p.y)
    }
}
A().f()

Swift 4.1 中会出现以下运行时错误,同时访问了p.y

Simultaneous access to 0x10217f930, but modification requires exclusive access

这个问题 tricky 的地方在于,如果你直接操作类型 ZZZ,得到的是编译时错误;但是如果你通过了protocol一层间接访问,它隐藏成了运行时错误。解决方法:返回值提取中间变量,再赋值。

3. 去除警告

完成以上两步,我们迁移的基本工作已经完成了,有时间的话,应当开展或者提前开始第三步,去除警告。警告不应该累积,以免不得不改的时候改动量太大。我们来看一些典型的:

  • Sequence.flatMap 返回 Optional 的情况下,应该使用 compactMap

  • deallocate(capacity:) 参数没用,直接去掉

  • protocol 中的 weak property 声明中,weak应该去掉

小结

在本文中,我们讨论了Swift 4.1 迁移中的发现:

  • 编译的问题和解决方案

  • 运行时的 Bug 和『特性』,及其解决方案

  • 需要注意去除的警告

  • 编译速度总体提升



历史文章回顾







以上是关于Swift 4.1 迁移指南的主要内容,如果未能解决你的问题,请参考以下文章

Greenplum迁移指南

如何在 Swift 中进行轻量级 CoreData 迁移

从 React 到 Preact 迁移指南

Android NavHostFragment(片段)膨胀失败,ViewBinding(使用导航组件)

如何在 Xcode 9.3 中切换到 Swift 4.0?

Swift Opaque Type vs Protocols - 文档推断协议的函数不能嵌套