昨天下午,我提交了基于 Swift 3.0 的奇点 for 微博 2.1.1 版本,主要是适配了 Swift 3.0 + 一些 Bug 修复。在适配 Swift 3.0 的过程中,我记录了一些常见的问题,相信所有在适配过程中的朋友都会遇到,于是总结这么一篇文章分享一下。

适配 Swift 3 的一点小经验和坑

适配 Swift 3 的一点小经验和坑 - 敏捷大拇指 - 适配 Swift 3 的一点小经验和坑





0、前言

奇点项目是一个小型的纯 Swift 的 iOS 项目,十余个第三方库 + 超两万行代码。它是我在 2014 年一开始就接触 Swift 以来的一个 Objective-C 项目用 Swift 重写的实验作品,后来把它做成了产品推出来。所以它存在的价值之一就是为了适配新一代 Swift。

所以 Swift 3.0 出来后,我咬咬牙,就决定立即开始适配它了。大概花了约一个晚上+一个白天的时间我就完成了对适配工作,同时淘汰了一些不兼容 Swift 3 的三方库,可以说是神清气爽。

那么下面我就开始一一列举适配 Swift 工作中遇到的问题和一些新习得的概念吧。




1、Any vs AnyObject

将项目里的 AnyObject 转成 Any 可能是大家遇到的第一件适配大事。如何解释这个变化呢?在 Swift 3 之前,我们可以写完一个项目都只用 AnyObject 来代表大多数实例,好像不用与 Any 类型打交道。但事实上,Any 和 AnyObject 是有明显区别的,因为 Any 可以代表 struct、class、func 等等几乎所有类型,而 AnyObject 只能代表 class 生成的实例。

那为什么之前我们在 Swift 2 里可以用 [AnyObject] 声明数组,并且在里面放 Int、String 等 struct 类型呢?这是因为 Swift 2 中,会针对这些 Int、String 等 struct 进行一个 Implicit Bridging Conversions,在 Array 里插入他们时,编译器会自动将其 bridge 到 Objective-C 的 NSNumber、NSString 等类型,这就是为什么我们声明的 [AnyObject] 里可以放 struct 的原因。

但在 Swift 3 当中,为了达成一个门真正的跨平台语言,相关提案将 Implicit Bridging Conversions 给去掉了。所以如果你要把 String 这个 struct 放进一个 [AnyObject] 里,一定要 as NSString,这些转换都需要显示的进行了——毕竟 Linux 平台默认没有 Objective-C runtime。这样各平台的表现更加一致。当然这是其中一个目标,具体可见 https://github.com/apple/swift-evolution/blob/master/proposals/0116-id-as-any.md 和相关提案的讨论。




2、@discardableResult 的使用

在 Swift 3 编译器下,如果一个 func 返回了一个对象,而你没有使用它时,会有一个 WARNING。对于追求项目洁癖(不想看到 1 个 WARNING 和 1 个 ERROR)的人来说这是不能忍的,尽管你可能是故意不去使用它的。

这里有两种方法可以解决这个 WARNING。

第一种:在 func 定义的前面,加上 @discardableResult 的修饰符,代表可以不使用返回值,这样编译器就不会有警告了。在我们自己定义的 func 上基本上都可以这么做。

但是还会有一种情况,用了第三方库或者系统库返回的对象怎么办?那只能用第二种办法:

[Swift] 纯文本查看 复制代码
_ = navigationController?.popViewController(animated: true)


像这样通过 _ 来省略掉了。虽然比较难看,考虑到 Swift 是一门严格的语言,就忍忍吧。




3、Protocol 实现一定要在对应的 extension 里

以前写代码时会有不注意的地方,比如 UITableViewDelegate 和 UITableViewDataSource,我分别用 extension 来实现,但是在具体的实现中没注意,把 delegate 的一个方法放进了 dataSource 的 extension 去实现,项目也能完全正常运作。

但是在 Swift 3.0 下,如果你在一个 extension 里实现一个 protocol,那么这个 protocol 的方法一定要在这个 extension 里面能找到,而不能在另外一个 extension 里或者主 class 或 struct 里面。不然会有类似这样的警告:

[Swift] 纯文本查看 复制代码
Objective-C method 'tableView:canEditRowAt:' provided by method 'tableView(_:canEditRowAt:)' does not match the requirement's selector ('tableView:canEditRowAtIndexPath:')


这也是 Swift 3 编译变得更严格的一个表现。




4、Implicitly Unwrapped Optionals 的坑

在 Swift 2 的项目中,我们可能存在这样不是特别安全的代码:

[Swift] 纯文本查看 复制代码
var greetings: String!
greetings = "Hello"
print("\(greetings) 图拉鼎")


这里会输出:

[Swift] 纯文本查看 复制代码
Hello 图拉鼎


没有任何问题。但是在 Swift 3 中,因为 Optional 的安全机制起作用了,会变成:

[Swift] 纯文本查看 复制代码
Optional("Hello") 图拉鼎


这个结果不是我们想要的。从这点也可以看到,Swift 3 的 IUO 行为变得更安全了,默认会把 IUO 变成 Optional。如果想要达到和 Swift 2 一样的效果,就得用:

[Swift] 纯文本查看 复制代码
print("\(greetings!) 图拉鼎")


这时你也注意到了,Swift 始终在用 ! 提醒你用 IUO 不那么安全。

所以趁这个机会可以好好重构一下老代码吧~(知乎暂时不支持 Swift 语言的代码高亮,我已经反馈该问题了)




5、转化器一些奇怪的坑

如果你是用 Xcode 的自动转换器来转 Swift 3 项目的,应该会遇到一些奇怪的坑,特别举例:

  • UIControlState.normal 会转换成 UIControlState(),还好其他状态不会这样,所以全局搜索一次再替换即可;
  • IndexPath 明明是一个去 NS 的 Foundation 类型了,但是自动转换后代码经常会有 (indexPath as NSIndexPath) 这样的东西,完全可以去掉;
  • 所有 NSNotification.Name 都可以重构成 Notification.Name,但是转换器并没有做这件事情。与其同时,自己用字符串定义的 NotificationName,现在可以统一基于 extension 去扩展啦。





6、后记

适配完奇点这个两万多行代码的项目后,实际上可以总结的远远不止上面这些,我只是挑了一些典型来讲。更何况,我只是完成了第一步「适配」而已,更多的工作,比如把 API 命名更加 Swift 3 化,充分利用 Swift 3 的一些特性来重构项目等等都还没有做。

所以在接下去的路上,应该还会去总结和学习一些东西吧。祝大家适配 Swift 3 愉快



推荐再看看全球最大的Swift开发者社区敏捷大拇指Swifthumb.com)上的《Swift 3 迁移工作总结




升级到Swift 3.0的适配填坑相关内容

如何向Swift 3.0进行数据迁移升级?Migrating Advice, tips,and warnings

Xcode 8 & Swift 3.0 迁移适配实战经验总结,含第三方库

Swift 3 迁移工作总结

是时候适配 Swift 3 了吗——专访 LINE iOS 开发工程师王巍

迁移到Swift 3.0之@discardableResult Swift 3.0 Migration Unused Result

Swift 3 迁移适配总结

AFNetworking 3.0迁移指南 AFNetworking 3.0 Migration Guide

报告:Swift 3.0 已出坑,适配 iOS 10、项目迁移 Swift 3.0 问题总结

]适配 Swift 3 的一点小经验和坑

Swift 3,这些陷阱在等你,来看我填坑吧 Swift 3 migration pitfalls

Swift 4.0

Swift 3.0 开发文档

Swift升级迁移适配填坑




作者:图拉鼎