HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

拥抱 Swift!优酷 Mac 迁移 Swift 实践

  • 2020-05-08
  • 本文字数:4773 字

    阅读完需:约 16 分钟

拥抱Swift!优酷Mac迁移Swift实践

一、背景

随着 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 没有的。


  1. 安全编程


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


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


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


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


  1. 编程范式的丰富


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


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


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


  1. 其它


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

三、迁移实战

  1. 从哪里开始迁移?


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


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



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


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


因此,如需要将 OC 迁移到 Swift,建议按照“从上到下”的原则进行迁移。这样既保证了工作量不会太大(不用去写一些 OC 调用 Swift 的适配代码)也保证了迁移后的稳定性,测试


只需回归一下迁移过的业务模块即可,还可以快速定位问题。


  1. 如何使用 Swift 的值类型


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


var a = 1var b = ab = 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: String   let videoTitle: String   let 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 Id
case .singleVideo(let videoId): // buy single video with video id } }}
复制代码


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


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


Trade.VIP(userId: "349951").buy()
复制代码


非常的简洁明了。


  1. 混编问题


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


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


// method in OC file- (void)someMethod {   A *a = [A new];   [a doWork];}
// class in A.swiftclass 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.swiftclass A: NSObject {   @objc func doWork() {      // do something   }}
复制代码


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


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


  1. 在 Swift 那些消失的东西


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


1)@synchronized


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


@synchronized 本质上来讲是一个互斥锁,背后其实是调用了 objc_sync_enterobjc_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 = <a href="">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() }}</a href="">
复制代码


我们利用 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,那我的答案是:现在。


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


2020-05-08 17:571468

评论

发布
暂无评论
发现更多内容

python scrapy极细拆解,打开Spider类看内容,顺手爬了一下优设网

梦想橡皮擦

12月日更

【量化】实战获取资产组合理论模型的数据源

恒生LIGHT云社区

资源 量化投资 量化

Android C++系列:Linux网络(二)通信过程

轻口味

c++ android 28天写作 12月日更

大厂面试算法题之链表

程序员学长

李飞飞力荐:阿里巴巴高可用数据库解决方案

博文视点Broadview

管人理事

张老蔫

28天写作

通过接口上传文件到百度网盘

为自己带盐

28天写作 百度网盘 签约计划第二季 12月日更

好习惯影响孩子的一生

Tiger

28天写作

如果TGO是经纪人,我们会怎么办?(9/28)

赵新龙

28天写作

第三天用 Mac,我安装了这些玩意

悟空聊架构

Mac 28天写作 悟空聊架构 12月日更

年度重磅!华为云2021应用构建技术实践精选集,免费下载!

华为云开发者联盟

数据库 大数据 云原生 数字化 华为云

Go语言学习查缺补漏ing Day6

恒生LIGHT云社区

golang 编程语言

基于MRS-Hudi构建数据湖的典型应用场景介绍

华为云开发者联盟

数据仓库 数据湖 华为云 Apache Hudi MRS-Hudi

Perforce用户文章转载:用了P4这一招,九成问题能自救

龙智—DevSecOps解决方案

报错 perforce

模块一课程作业

李晓笛

前端面试题之模块化开发

@零度

大前端 模块化

人人都能读懂的react源码解析(大厂高薪必备)

buchila11

React React Hooks

2.react心智模型(来来来,让大脑有react思维吧)

buchila11

React

架构师实战营模块一作业

圈圈gor

「架构实战营」

【LeetCode】二叉搜索树中的搜索Java题解

Albert

算法 LeetCode 12月日更

Go语言逆向技术:恢复函数名称算法

华为云开发者联盟

二进制 函数 go语言 逆向分析 恢复函数名称

万众提供素材,万众联合创作

mtfelix

28天写作

java开发之java开发环境的快速构建

@零度

Java java开发环境

dart系列之:浏览器中的舞者,用dart发送HTTP请求

程序那些事

flutter 浏览器 dart 程序那些事 12月日更

了解 Flutter 的Timer类和Timer.periodic【Flutter专题19】

坚果

flutter 28天写作 签约计划第二季 12月日更

Flutter 详解 CupertinoSegmentedControl 分段控制器

阿策小和尚

28天写作 0 基础学习 Flutter 内容合集 签约计划第二季 12月日更

给弟弟的信第7封|离开大学的喜与悲

大菠萝

28天写作

API标准化对Dapr的重要性

行云创新

数据分析从零开始实战专栏导航@老表

老表

Python 数据库 数据分析 pandas 数据分析从零开始实战

【报名中】我们把你对 ShardingSphere 的好奇,都放在这场 Meetup 中

SphereEx

数据库 开源社区 ShardingSphere Meetup SphereEx

高效设计一个LRU

bigsai

数据结构 算法 LRU

拥抱Swift!优酷Mac迁移Swift实践_文化 & 方法_阿里巴巴文娱技术_InfoQ精选文章