作者丨阿里文娱高级无线开发工程师 大斗

不管从“明里”还是“暗里”来看,苹果都在大力推荐使用 Swift 这一门语言。作为苹果的“亲儿子”,相信 Swift 语言将会是开发 MacOS 和 iOS 的第一选择。

一、背景

随着 Swift 5.0 的发布,Swift 的 ABI 终于稳定下来了。如果是很早就拥抱 Swift 的开发者,一定经历过各 Swift 大版本发布时的痛苦。回想在前一家公司将 Swift 2.2 升级到 Swift 3.0,基本上是换了个语言,两个版本之间的差异非常之大,升级起来简直是苦不堪言。

另外 ABI 的稳定也让 Swift 运行时环境可以随着苹果系统(iOS, Mac OS, Watch OS, TV OS)一起发布,不用再将 Swift 加入应用包,减小了包的体积。

所以,如果以前不使用 Swift 的原因之一,是 Swift 不稳定造成的开发成本过大,那随着 Swift 5.0 的发布,终于可以抛开这个顾虑了。

二、为什么迁移 Swift

当然,仅仅就 ABI 稳定这一个原因,肯定不足以说服我们将 Objective-C(以下简称 OC)迁移到 Swift,我们还得看看使用 Swift 能够给我们带来什么东西是 OC 没有的。

安全编程

Swift 是一门静态语言,虽然没有 OC 动态特性的灵活,但是这也让 Swift 更加安全。当然,静态语言非常多,为何 Swift 会特意强调“安全”这一特性?因为 Swift 在语言层面做了很多工作。

(1) 可选值。可选值的引入明确了这样一个问题:这个值是否存在。这使得我们在处理某个值的时候,能够很清楚的知道是否应该去判断这个值的有无,避免了不必要的 crash 问题;

(2) 值类型。Swift 中的 Struct 是一个值类型,它和引用类型的最大区别就是,将一个值类型赋给另外一个变量时,是通过值拷贝完成的(当然 Swift 用了 Copy-on-Write 的技术保证性能),我们就不用担心拷贝之后使用的安全问题,不用担心新变量的值修改之后会影响到原来的值;

(3) 更多安全的关键字。guard让我们在执行接下来的代码前保证某一个条件的成立,并且使程序可读性更高;defer避免我们忘记在代码块执行完毕后所需要执行的清理工作。

编程范式的丰富

(1) 支持函数式编程。函数作为 Swift 中的一等公民,Swift 可以支持函数式编程。我们可以使用函数式编程的无状态性,不可变性,无副作用这些特性写出更健壮的代码;

(2) 面向协议编程。Swift 中也有协议 protocol,不过 Swift 中的 protocol 比起 OC 中的 protocol 强大太多。我们可以扩展协议给协议中的方法给一个默认实现。这样我们就可以在不改动已有类或 Struct 的前提下添加能力,非常的方便;

(3) 强大泛型。泛型的引入可以让我们编写一些更加通用的代码,使代码更加灵活,可用性更高。

其它

如没有头文件减轻了复杂性,让代码量更少;利用元组(tuple)支持多返回值减少了一些不必要的模型等等。这些都使代码更简洁,更清晰。

三、迁移实战

从哪里开始迁移?

Swift 和 OC 可以相互调用,但是由于 Swift 新增了一些新的数据结构,如 Enum、 Struct 等,因此 OC 调用 Swift 时有一定的局限性,需要做的一些额外的工作。反过来,当 Swift 调用 OC 时则容易得多。

一般来说,大多数 iOS 工程都有如下结构:

从上图可以看出,UI View Controller 和 UI View 都是在最上层,很少有其它模块依赖它们。即使有,也是其它模块的 UI View Controller 或 UI View 对其有依赖。因此,我们在迁移的时候可以从 UI View Controller 和 UI View 相关类进行迁移。这样的话,将这些类迁移为 Swift 后,可以顺利的调用 OC 相关的类和 API。

另外,从稳定性的角度来看,从上到下的迁移也更安全。如果我们从下层的一些中间件或基础库开始迁移的话,由于上层的大多数业务模块都对中间件或者基础库有依赖,我们对下层模块的修改就会影响多个业务模块,而且通常不知道这些下层模块到底被上层的哪些模块所调用。如果修改出现问题就会影响到非常多的模块,更糟的是,如果是一些使用频率比较少的业务场景对这些下层模块有依赖,那么可能在开发过程中很难发现问题,不知不觉的就带到线上,造成比较大的影响。

因此,如需要将 OC 迁移到 Swift,建议按照“从上到下”的原则进行迁移。这样既保证了工作量不会太大(不用去写一些 OC 调用 Swift 的适配代码)也保证了迁移后的稳定性,测试只需回归一下迁移过的业务模块即可,还可以快速定位问题。

如何使用 Swift 的值类型

我们都知道 Swift 引入了两个重要的数据结构 Struct 和 Enum,这两个都是值类型。值类型我们都知道是非常安全的,例如下面这段代码:

var a = 1
var b = a
b = 2

我们可以随意更改 b 的值而不用担心 a 的值会受任何影响,因为值类型的赋值都是通过拷贝来进行的(并使用 Copy-on-Write 的技术来保证性能)。另外,值类型相较于引用类型来说,减少了堆上的内存分配和回收次数。理论上来说,如果能用 Struct 类型就尽量使用 Struct 类型。

@interface Video: NSObject@property (nonatomic, copy) NSString *videoId;
@property (nonatomic, copy) NSString *videoTitle;
@property (nonatomic, copy) NSString *videoSubtitle;@end

例如我们有一个 Model 叫 Video,然后我们用 Struct 可以写成这样:

struct Video {let videoId: Stringlet videoTitle: Stringlet videoSubtitle: String
}

这样改写不仅将我们的 Model 改成更加安全的值类型 Struct,还利用了 let 关键字将里面的属性改为不可变的,使代码更加安全。

哪些又需要改成 Enum 类型呢?Enum 类型特别适合那种有明显种类区别的场景。例如以下代码:

typedef NS_ENUM(NSUInteger, TradeType) {TradeTypeVip = 0,TradeTypeSingleVideo
};@interface TradeManager: NSObject- (void)buyWithType:(TradeType)type;@end

比如我们的支付场景分为购买 VIP 会员 ,购买单片。在 OC 中我们会定义一个 enum 来区分不同的购买类型,然后通过TradeManager相关 API 来进行购买。

[[TradeManager sharedManager] buyWithType:TradeTypeVip]

而在 Swift 中我们可以把它改成这样:

enum Trade {case VIP(userId: String)case singleVideo(videoID: String)func buy() {switch self {case .VIP(let userId):// buy vip with user Idcase .singleVideo(let videoId):// buy single video with video id}}
}

我们把购买的逻辑全部放到 enum 中,利用 enum 的关联值来进行相关参数的传递,而且 Swift 中的 enum 类型可以添加方法,所以我们可以把购买的业务逻辑都放到一起,通过 switch 来进行判断。

然后我们就可以这样使用:

Trade.VIP(userId: "349951").buy()

非常的简洁明了。

混编问题

虽然我们可以按照“从上到下”的原则开始迁移,但是即使是这样也免不了需要 OC 去调用 Swift 的代码,有一些地方还是得处理一下。

我们都知道,OC 是一门动态语言,所有对象都是基于运行时的。而 Swift 则是一门静态语言,除了某一些特性可能需要在运行时完成(如反射),绝大部分的工作都是在编译时就确定了的(例如 Swift 类型的成员变量或方法)。我们来看一段代码:

// method in OC file
- (void)someMethod {A *a = [A new];[a doWork];
}// class in A.swift
class A: NSObject {func doWork() {// do something}
}

上面的代码中,能编译通过吗?

当然不能,因为 Swift 的类型缺少一些运行时所需要信息,会导致失败,编译器会报出No visible @interface for ‘CMSFilterViewController’ declares the selector ‘reloadWith:channelName:'的错误。解决方法也很简单,在所需要使用到的前添加@objc即可。

需要注意的是,Swift 的 class 类型必须继承 NSObject 才能被 OC 所调用。

// class in A.swift
class A: NSObject {@objc func doWork() {// do something}
}

这样,OC 就能够找到 Swift 类型中相应的方法 (属性同理)。

必须说明的一点是,标记为 @objc 并不意味着这个方法就是动态派发的,它依然是静态调用。如果想要运行时相关的特性,必须使用 dynamic 关键字,这里不再赘述。

在 Swift 那些消失的东西

在迁移到 Swift 的过程中,我们会发现某些代码并不能在 Swift 中找到对应类或者方法来处理,下面是一些典型的例子。

1) @synchronized

在性能要求不是太高的情况下,我们通常会使用@synchronized来为一个对象加上锁,而 Swift 已经没有相关的关键字了,所以我们需要做一些额外的工作。

@synchronized本质上来讲是一个互斥锁,背后其实是调用了 objc_sync_enter 和 objc_sync_exit 方法来实现的,所以,我们可以自己写一个类似的方法:

func synchronized(_ lock: AnyObject, block: () -> Void) { objc_sync_enter(lock)block()objc_sync_exit(lock)
}

在使用时我们利用 Swift 的 Trailing Closure 可以写出类似 OC 的代码,非常的优美:

func addObject(obj: AnyObject) {synchronized(self) { // do something}
}

2) 单例

在 OC 中,我们的单例基本都是这样写的:

+ (instancetype)sharedInstance {static id sharedInstance = nil;static dispatch_once_t onceToken = 0;dispatch_once(&onceToken, ^{sharedInstance = [[self alloc] init];});return sharedInstance;
}

而在 Swift 中,我们直接定义一个静态常量就可以定义一个单例:

static let shared = YourObject()

不仅代码量更少,意义也更加明确。

3) dispatch_once

就像上面 OC 代码那样,一般是使用 dispatch_once 来实现一个单例。但是 Swift 中已经没有 dispatch_once 这个方法了,那如果非要要使用的话应该怎么办呢?我们可以这样定义:

public extension DispatchQueue {private static var _onceTokens = [String]()public class func once(token: String, block:()->Void) {objc_sync_enter(self)defer { objc_sync_exit(self) }if _onceTokens.contains(token) {return}_onceTokens.append(token)block()}
}

我们利用 Swift 的extension给 DispatchQueue 添加一个类方法,然后可以这样使用:

DispatchQueue.once(token: "oncetoken") {// do something
}

当然,OC 和 Swift 区别远远不止于此,包括 Swift 对 C 的调用,日志的打印等等都有很多可以深究的点,限于篇幅原因就不再赘述。

四、计划和展望

目前优酷 Mac 端还在继续迁移中,一方面需要进行正常的业务迭代,并不能投入太多的人力专门进行迁移,目前的做法是新的需求使用 Swift 进行开发,如果有依赖到原来 OC 的相关模块,根据工作量来进行一部分的迁移;另一方面就是考虑到项目的稳定性,也不会直接把所有 OC 代码迁移到 Swift 上,逐步迁移也方便测试人员进行针对性的回归。

Swift 所带来的肯定不只是语言层面的这些优点。WWDC 2019 年发布的 Swift UI 不仅可以使用更加简洁的语法来进行 UI 开发,最重要的是可以使用同一个 UI 组件库来开发 Mac OS 和 iOS 上的界面,让一套代码在 Mac 和 iOS 设备上运行提供了可能性。

另外,Swift 作为一个跨平台语言不只是在苹果相关的平台上运行,目前 Swift 还支持 Linux 系统,我们可以在 Linux 系统上将 Swift 作为开发语言进行开发。目前已经有跨 Android 和 iOS 的 UI 库 SCADE,可以让我们同一套代码来开发 Android 和 iOS 的界面。

可能也有人会问,跨平台现在有了 Flutter,我们还学习 Swift 干嘛呢?确实 Flutter 作为一个非常优秀的跨平台方案,它有着优秀的渲染性能,并且支持非常多的平台(iOS、Android、Mac OS 甚至是 Windows)。但是我们也知道,Flutter 是一个 UI 组件库,它可以帮助我们解决一部分 UI 问题,但是再往下呢?还是得使用 OC 或者 Swift。有人也会说直接用 OC 不就好了。可是我们可以看到一个现象,现在苹果的官方文档上面,基本上都是使用 Swift 来编写相关的代码示例,苹果也是在慢慢的“抛弃”OC 这一门语言。

不管从“明里”还是“暗里”来看,苹果都是在大力推荐使用 Swift 这一门语言。作为苹果的“亲儿子”,相信 Swift 语言将会是开发 MacOS 和 iOS 的第一选择。

所以,如果有人问我什么时候可以开始学习 Swift,那我的答案是:现在。

拥抱Swift!优酷Mac迁移Swift实践相关推荐

  1. Flutter有局限,拥抱Swift!优酷Mac迁移Swift实践

    Python实战社群 Java实战社群 长按识别下方二维码,按需求添加 扫码关注添加客服 进Python社群▲ 扫码关注添加客服 进Java社群▲ 作者丨阿里文娱高级无线开发工程师 大斗 来源 | I ...

  2. 拯救Mac OSX的SSD!优酷Mac客户端缓冲(下载)视频路径转移,修改下载文件夹

    拯救Mac OSX的SSD!优酷Mac客户端修改下载路径 前言: 优酷客户端默认使用系统分区缓存视频文件,默认路径是/Users/{你的用户名}/Library/Containers/com.youk ...

  3. 优酷超高清视频技术实践

    经过多年的技术发展,目前互联网视频的观看视频体验仍然无法满足用户预期,因此我们需要应用更新的技术来持续超越传统在线观影体验.与此同时,长周期.大投入的视频内容源,成为"超高清"大规 ...

  4. 优酷 DSP 系统建设实践与思考 | 完整PPT

    随着 RTB 网络在线展现广告交易模式的兴起,各大公司都纷纷搭建自己的 DSP ( Demand-Side Platform ) 广告投放系统进行获客.优酷在近几年也搭建 DSP 系统,并且在持续迭代 ...

  5. 行业首发:响应式优酷快速适配新Mac

    阿里妹导读:苹果计划两年时间全线过度到M1芯片,未来M1用户必将成为主流,而M1新机型支持iOS app直接运行不必再采用MacCatalyst的方式,可以基于iOS app直接为M1用户提供同样的端 ...

  6. 优酷智能档的设计、实现和应用

    简介: 如果你用优酷客户端追剧,会发现现在选择清晰度没那么麻烦了.以前在地铁上为了"不 卡"用低清晰度,在家里的 Wi-Fi 环境为了看得更舒服再手动换 1080P.现在清晰度选择 ...

  7. Swift 首次调试断点慢的问题解法 | 优酷 Swift 实践

    作者:段继统 & 夏磊 调试断点是与开发体验关系最为密切点之一,优酷iOS团队在外部调研时候发现,大量国内的iOS APP研发团队也遇到了类似的问题.考虑到国内Swift如火如荼的现状,我们尽 ...

  8. 优酷鸿蒙开发实践|优酷 Android 与HarmonyOS Hap 混合打包

    在<优酷鸿蒙开发实践|鸿蒙卡片开发>一文中已经提到,要实现"在优酷主客ICON向上滑动,呼出优酷鸿蒙卡片",需要卡片的实现代码与优酷主客做混合打包.下面的小节简单介绍了 ...

  9. 优酷IPv6改造纪实:视频行业首家拥抱下一代网络技术

    阿里妹导读:2018年双11前,优酷开启了IPv6的大门.9月份PC端业务开启灰度,迎来首位IPv6 VIP用户后,优酷移动客户端也马不停蹄地加入灰度大军.从0到1,花了几个月:从10到1000,花了 ...

最新文章

  1. 绝对强大的三个LINUX指令: AR, NM, OBJDUMP
  2. 计算机对英语口语考试成绩,英语口语考试人机对话得分技巧
  3. 如何通过代码获得当前SAP Spartacus Component渲染所基于的slot名称
  4. 关于Angular里给Component protected方法写单元测试的技巧
  5. 多域名解析及延伸知识点
  6. 浅谈嵌入式系统的持续集成
  7. gradle 失败 编译项目_maven常见问题处理(3-3)Gradle编译时下载依赖失败解决方法...
  8. 数组常见异常 学习笔记
  9. metamask源码学习-ui/index.js
  10. 桌面版IDE将迎终结,Github发布代码空间Codespaces | 凌云时刻
  11. 久其报表节点汇总_久其通用数据管理平台常见公式
  12. win8.1下安装vc6
  13. 模压硅胶产品成型后加工工艺
  14. 软件开发通识之一:什么是计算机语言?
  15. 谷歌浏览器手动同步设置
  16. 哥白尼气候数据ERA5数据集——大气数据研究
  17. matlab如何去除图像白边_matlab 图像保存时去除白边
  18. x230可以装win10吗_联想x230i笔记本用U盘安装win10专业版的操作教程
  19. C语言DNA序列的编码,DNA (C语言代码)
  20. php经纬度之间的距离计算公式,golang与php实现计算两个经纬度之间距离的方法

热门文章

  1. vue.esm.js版本 引入DatePick时 会报错
  2. STM32读取BWT901CL传感器数据
  3. Revit中墙体绘制的小技巧?CAD识别墙体快速生成
  4. 2021版安装cocoapods
  5. 计算机基本配置检测单,怎么查看与测试PC电脑的硬件配置
  6. Word参考文献对齐的设置方法,从[1]-[99]全部都对齐
  7. 节操视频播放 -- 简单
  8. python学习笔记,pycharm注释有黄色下划线,已解决
  9. VantUI时间选择器
  10. wps公式编辑器 WPS怎么备份 如何恢复未保存的文件