OC项目转Swift经验总结

运行环境:Xcode 11.1 Swift5.0
最近在参与一个项目从Objective-C(以下简称OC)转到Swift的过程,期间遇到了很多坑,这是一篇采坑日志。如果你也在经历项目的Swift化,相信会有一定的参考意义。

OC转Swift有一个大前提就是你要对Swift有一定的了解,最好是用Swift开发过一个项目,否则不建议直接做这种转换的尝试。我选择迁移的原因是自己使用Swift开发有一年多了,后来转移到了一个OC的项目组。而这个项目主要是我一个人负责,因为感觉Swift更顺手,才会有了这个项目迁移重构的过程。

自动化工具

有一个比较好的自动化工具Swiftify,可以将OC文件甚至OC工程整个转成Swift,号称排名第一的OC转Swift工具,准确率能达到90%。我试用了免费版中单文件的转化,但感觉效果并不理想,因为没有使用过付费版,所以也不好评价它就是不好。

另外Swiftify还有一个Xcode的插件Swiftify for Xcode,可以实现对选中代码和单文件的转化。这个插件还挺不错,对纯系统代码转化挺精确,对于第三方库的代码识别就不太好了,存在一些识别问题,还需要手动再修改。

虽然工具总是存在一些问题,但是对于某些场景来说比着一点一点改还是要更有效率的。

手动Swift化

项目由OC转Swift的过程本来就不是简单的”翻译“一下就完事了,Swift存在很多独有的特性,在转换工程中我们需要注意。

桥接文件

如果你是在项目中首次使用Swift代码,在添加Swift文件时,Xcode会提示你添加一个.h的桥接文件。如果不小心点了不添加还可以手动导入,就是自己手动生成一个.h文件,然后在Build Settings > Swift Compiler - General > Objective-C Bridging Header中填入该.h文件的路径。

这个桥接文件的作用就是供Swift代码引用OC代码,或者OC的三方库。

1
2
#import "Utility.h"
#import <Masonry/Masonry.h>

系统API

对于UIKit框架中的大部分代码转换可以直接查看系统API文档进行转换,这里就不过多介绍。

property(属性)和方法

Swift没有property,只有一个表示属性是否可变letvar,这个根据需求处理就行了。

注意点一
OC中一个类分.h.m两个文件,分别表示用于暴露给外接的方法,变量和仅供内部使用的方法变量。迁移到Swift时,应该将.m中的property标为private,即外接无法直接访问,对于.h中的property不做处理,取默认的internal,即同模块可访问。

对于函数的迁移也是相同的。

注意点二
有一种特殊情况是在OC项目中,某些属性在内部(.m)可变,外部(.h)只读。这种情况可以这么处理:

1
private(set) var value: String

就是只对valueset方法就行private标记。

注意点三
Swift中针对空类型有个专门的符号?,对应OC中的nil。OC中没有这个符号,但是可以通过在nullablenonnull表示该种属性,方法参数或者返回值是否可以空。

如果OC中没有声明一个属性是否可以为空,那就去默认值nonnull

如果我们想让一个类的所有属性,函数返回值都是nonnull,除了手动一个个添加之外还有一个宏命令。

1
2
3
NS_ASSUME_NONNULL_BEGIN

NS_ASSUME_NONNULL_END

懒加载

OC代码:

1
2
3
4
5
6
- (MTObject *)object {
if (!_object) {
_object = [MTObject new];
}
return _object;
}

Swift代码:

1
2
3
4
lazy var object: MTObject = {
let object = MTObject()
return imagobjecteView
}()

闭包

OC代码:

1
typedef void (^DownloadStateBlock)(BOOL isComplete);

Swift代码:

1
typealias DownloadStateBlock = ((_ isComplete: Bool) -> Void)

单例

OC代码:

1
2
3
4
5
6
7
+ (MTDownloadManager *)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}

Swift代码:

1
static let shared = MTDownloadManager()

id类型

初始化方法和函数销毁

OC先调用父类的初始化方法,然后初始自己的成员变量。
Swift先初始化自己的成员变量,然后在调用父类的初始化方法。
OC代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 初始化方法
- (instancetype)init:(NSString *)title {
self = [super init];
if (self) {
MTModel *model = [MTModel new];
model.isSelected = NO;
self.array = @[model];
self.title = title;
}
return self;
}
/// 函数销毁方法
- (void)dealloc {
//dealloc
}

Swift代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/// 初始化方法
override init(title: String) {
// 注意该属性赋值在父类方法之前
self.title = title
super.init()
let model = MTModel()
model.isSelected = false
self.array = [model]
}
/// 函数销毁方法
deinit {
//deinit
}

NSNotification(通知)

delegate(代理)

1
2
3
4
@protocol MTDownloadDelegate <NSObject>
- (void)downloadFileFailed:(NSError *)error;
- (void)downloadFileComplete;
@end
1
2
3
4
@objc protocol MTDownloadDelegate {
func downloadFailFailed(error: Error)
func downloadFileComplete()
}

OC和Swift相互调用

配置项

初次引入swift文件会提示我们增加桥接头文件,我们添加就行,如果忘记添加,可以在Build Settings中搜索Objective-C Bridging Header,手动加进去。
这个桥接头文件是引用OC代码的头文件,其实引用Swift代码也是有对应头文件的,是ProjectName-Swift.h。它的配置位置就在Bridging Header的下面,设置项叫Objective-C Generated Interface Header Name。这个是系统自动添加的,该文件是隐藏文件,可以查看不可修改,用于将Swift调用转成对应的OC调用。

在OC中调用Swift代码

供OC调用的Swift代码有以下注意点:

  • Swift代码要想被OC调用,需要在属性和方法名前面加上@objc
  • Swift独有的特性,如泛型,struct,特殊enum等无法被OC调用。
  • 兼容OC的enum仅支持Int类型。

Swift代码会被编译成供OC调用的代码,但是Swift和OC本身在命名上就有些区别,这是需要注意的。

函数重命名

enum重命名

手动指定名字

Donate comment here