OC项目转Swift指南

OC项目转Swift指南

运行环境:Xcode 11.1 Swift5.0html

最近参与的一个项目须要从Objective-C(如下简称OC)转到Swift,期间遇到了一些坑,因而有了这篇总结性的文档。
若是你也有将OC项目Swift化的需求,能够做为参考。git

OC转Swift有一个大前提就是你要对Swift有必定的了解,熟悉Swift语法,最好是完整看过一遍官方的Language Guidegithub

转换的过程分自动化和手动转译,鉴于自动化工具的识别率不能让人满意,大部分状况都是须要手动转换的。面试

自动化工具

有一个比较好的自动化工具Swiftify,能够将OC文件甚至OC工程整个转成Swift,号称准确率能达到90%。我试用了一些免费版中的功能,但感受效果并不理想,由于没有使用过付费版,因此也很差评价它就是很差。swift

Swiftify还有一个Xcode的插件Swiftify for Xcode,能够实现对选中代码和单文件的转化。这个插件还挺不错,对纯系统代码转化还算精确,但部分代码还存在一些识别问题,须要手动再修改。xcode

OC项目转Swift指南

手动Swift化

桥接文件

若是你是在项目中首次使用Swift代码,在添加Swift文件时,Xcode会提示你添加一个.h的桥接文件。若是不当心点了不添加还能够手动导入,就是本身手动生成一个.h文件,而后在Build Settings > Swift Compiler - General > Objective-C Bridging Header中填入该.h文件的路径。多线程

OC项目转Swift指南

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>闭包

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

#import "Utility.h"
#import <Masonry/Masonry.h>
复制代码

Bridging Header的下面还有一个配置项是Objective-C Generated Interface Header Name,对应的值是ProjectName-Swift.h。这是由Xcode自动生成的一个隐藏头文件,每次Build的过程会将Swift代码中声明为外接调用的部分转成OC代码,OC部分的文件会相似pch同样全局引用这个头文件。由于是Build过程当中生成的,因此只有.m文件中能够直接引用,对于在.h文件中的引用下文有介绍。ide

Appdelegate(程序入口)

Swift中没有main.m文件,取而代之的是@UIApplicationMain命令,该命令等效于原有的执行main.m。因此咱们能够把main.m文件进行移除。

系统API

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

property(属性)

Swift没有property,也没有copynonatomic等属性修饰词,只有表示属性是否可变的letvar

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

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

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

private(set) var value: String
复制代码

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

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

若是OC中没有声明一个属性是否能够为空,那就去默认值nonnull

若是咱们想让一个类的全部属性,函数返回值都是nonnull,除了手动一个个添加以外还有一个宏命令。

NS_ASSUME_NONNULL_BEGIN
/* code */
NS_ASSUME_NONNULL_END
复制代码

这是个人iOS开发交流群:519832104无论你是小白仍是大牛欢迎入驻,能够一块儿分享经验,讨论技术,共同窗习成长!
另附上一份各好友收集的大厂面试题,须要iOS开发学习资料、面试真题,进群便可获取!
OC项目转Swift指南

点击此处,当即与iOS大牛交流学习

enum(枚举)

OC代码:

typedef NS_ENUM(NSInteger, PlayerState) {
    PlayerStateNone = 0,
    PlayerStatePlaying,
    PlayerStatePause,
    PlayerStateBuffer,
    PlayerStateFailed,
};

typedef NS_OPTIONS(NSUInteger, XXViewAnimationOptions) {
    XXViewAnimationOptionNone            = 1 <<  0,
    XXViewAnimationOptionSelcted1        = 1 <<  1,
    XXViewAnimationOptionSelcted2        = 1 <<  2,
}
复制代码

Swift代码:

enum PlayerState: Int {
    case none = 0
    case playing
    case pause
    case buffer
    case failed
}
struct ViewAnimationOptions: OptionSet {
    let rawValue: UInt
    static let None = ViewAnimationOptions(rawValue: 1<<0)
    static let Selected1 = ViewAnimationOptions(rawValue: 1<<0)
    static let Selected2 = ViewAnimationOptions(rawValue: 1 << 2)
    //...
}
复制代码

Swift没有NS_OPTIONS的概念,取而代之的是为了知足OptionSet协议的struct类型。

懒加载

OC代码:

- (MTObject *)object {
    if (!_object) {
        _object = [MTObject new];
    }
    return _object;
}
复制代码

Swift代码:

lazy var object: MTObject = {
    let object = MTObject()
    return imagobjecteView
}()
复制代码

闭包

OC代码:

typedef void (^DownloadStateBlock)(BOOL isComplete);
复制代码

Swift代码:

typealias DownloadStateBlock = ((_ isComplete: Bool) -> Void)
复制代码

单例

OC代码:

+ (XXManager *)shareInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
复制代码

Swift对单例的实现比较简单,有两种方式:

第一种

let shared = XXManager()// 声明在全局命名区(global namespace)
Class XXManager { 
}
复制代码

你可能会疑惑,为何没有dispatch_once,如何保证多线程下建立的惟一性?实际上是这样的,Swift中全局变量是懒加载,在AppDelegate中被初始化,以后全部的调用都会使用该实例。并且全局变量的初始化是默认使用dispatch_once的,这保证了全局变量的构造器(initializer)只会被调用一次,保证了shard原子性

第二种

Class XXManager {
        static let shared = XXManager()
    private override init() {
        // do something 
    }
}
复制代码

Swift 2 开始增长了static关键字,用于限定变量的做用域。若是不使用static,那么每个shared都会对应一个实例。而使用static以后,shared成为全局变量,就成了跟上面第一种方式原理一致。能够注意到,因为构造器使用了 private 关键字,因此也保证了单例的原子性。

初始化方法和析构函数

对于初始化方法OC先调用父类的初始化方法,而后初始本身的成员变量。Swift先初始化本身的成员变量,而后在调用父类的初始化方法。

OC代码:

// 初始化方法
@interface MainView : UIView
@property (nonatomic, strong) NSString *title;
- (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title NS_DESIGNATED_INITIALIZER;
@end

@implementation MainView
- (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title {
    if (self = [super initWithFrame:frame]) {
        self.title = title;
    }
    return self;
}
@end
// 析构函数
- (void)dealloc {
    //dealloc
}
复制代码

上面类在调用时

Swift代码:

class MainViewSwift: UIView {
    let title: String
    init(frame: CGRect, title: String) {
        self.title = title
        super.init(frame: frame)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
        deinit {
      //deinit
    }
}
复制代码

函数调用

OC代码:

// 实例函数(共有方法)
- (void)configModelWith:(XXModel *)model {}
// 实例函数(私有方法)
- (void)calculateProgress {}
// 类函数
+ (void)configModelWith:(XXModel *)model {}
复制代码
// 实例函数(共有方法)
func configModel(with model: XXModel) {}
// 实例函数(私有方法)
private func calculateProgress() {}
// 类函数(不能够被子类重写)
static func configModel(with model: XXModel) {}
// 类函数(能够被子类重写)
class func configModel(with model: XXModel) {}
// 类函数(不能够被子类重写)
class final func configModel(with model: XXModel) {}
复制代码

OC能够经过是否将方法声明在.h文件代表该方法是否为私有方法。Swift中没有了.h文件,对于方法的权限控制是经过权限关键词进行的,各关键词权限大小为: private &lt; fileprivate &lt; internal &lt; public &lt; open

其中internal为默认权限,能够在同一module下访问。

NSNotification(通知)

OC代码:

// add observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(method) name:@"NotificationName" object:nil];
// post
[NSNotificationCenter.defaultCenter postNotificationName:@"NotificationName" object:nil];
复制代码

Swift代码:

// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: NSNotification.Name(rawValue: "NotificationName"), object: nil)
// post
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NotificationName"), object: self)
复制代码

能够注意到,Swift中通知中心NotificationCenter不带NS前缀,通知名由字符串变成了NSNotification.Name的结构体。

改为结构体的目的就是为了便于管理字符串,本来的字符串类型变成了指定的NSNotification.Name类型。上面的Swift代码能够修改成:

extension NSNotification.Name {
    static let NotificationName = NSNotification.Name("NotificationName")
}
// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: .NotificationName, object: nil)
// post
NotificationCenter.default.post(name: .NotificationName, object: self)
复制代码

protocol(协议/代理)

OC代码:

@protocol XXManagerDelegate <NSObject>
- (void)downloadFileFailed:(NSError *)error;
@optional
- (void)downloadFileComplete;
@end

@interface XXManager: NSObject
@property (nonatomic, weak) id<XXManagerDelegate> delegate;  
@end
复制代码

Swift中对protocol的使用拓宽了许多,不光是class对象,structenum也均可以实现协议。须要注意的是structenum为指引用类型,不能使用weak修饰。只有指定当前代理只支持类对象,才能使用weak。将上面的代码转成对应的Swift代码,就是:

@objc protocol XXManagerDelegate {
    func downloadFailFailed(error: Error)
    @objc optional func downloadFileComplete() // 可选协议的实现
}
class XXManager: NSObject {
    weak var delegate: XXManagerDelegate?  
}
复制代码

@objc是代表当前代码是针对NSObject对象,也就是class对象,就能够正常使用weak了。

若是不是针对NSObject对象的delegate,仅仅是普通的class对象能够这样设置代理:

protocol XXManagerDelegate: class {
    func downloadFailFailed(error: Error)
}
class XXManager {
    weak var delegate: XXManagerDelegate?
}
复制代码

值得注意的是,仅@objc标记的protocol可使用@optional

Swift和OC混编注意事项

函数名的变化

若是你在一个Swift类里定义了一个delegate方法:

@objc protocol MarkButtonDelegate {
    func clickBtn(title: String)
}
复制代码

若是你要在OC中实现这个协议,这时候方法名就变成了:

- (void)clickBtnWithTitle:(NSString *)title {
    // code
}
复制代码

这主要是由于Swift有指定参数标签,OC却没有,因此在由Swift方法名生成OC方法名时编译器会自动加一些修饰词,已使函数做为一个句子能够"通顺"。

在OC的头文件里调用Swift类

若是要在OC的头文件里引用Swift类,由于Swift没有头文件,而为了让在头文件可以识别该Swift类,须要经过@class的方法引入。

@class SwiftClass;

@interface XXOCClass: NSObject
@property (nonatomic, strong) SwiftClass *object;
@end
复制代码

对OC类在Swift调用下重命名

由于Swift对不一样的module都有命名空间,因此Swift类都不须要添加前缀。若是有一个带前缀的OC公共组件,在Swift环境下调用时不得不指定前缀是一件很不优雅的事情,因此苹果添加了一个宏命令NS_SWIFT_NAME,容许在OC类在Swift环境下的重命名:

NS_SWIFT_NAME(LoginManager)
@interface XXLoginManager: NSObject
@end
复制代码

这样咱们就将XXLoginManager在Swift环境下的类名改成了LoginManager

引用类型和值类型

  • struct 和 enum 是值类型,类 class 是引用类型。
  • StringArray和 Dictionary都是结构体,所以赋值直接是拷贝,而NSStringNSArrayNSDictionary则是类,因此是使用引用的方式。
  • struct 比 class 更“轻量级”,struct 分配在栈中,class 分配在堆中。

id类型和AnyObject

OC中id类型被Swift调用时会自动转成AnyObject,他们很类似,但却其实概念并不一致。Swift中还有一个概念是Any,他们三者的区别是:

  • id 是一种通用的对象类型,它能够指向属于任何类的对象,在OC中便是能够表明全部继承于NSObject的对象。
  • AnyObject能够表明任何class类型的实例。
  • Any能够表明任何类型,甚至包括func类型。

从范围大小比较就是:id &lt; AnyObject &lt; Any

其余语法区别及注意事项(待补充)

一、Swift语句中不须要加分号;

二、关于Bool类型更加严格,Swift再也不是OC中的非0就是真,真假只对应truefalse

三、Swift类内通常不须要写self,可是闭包内是须要写的。

四、Swift是强类型语言,必需要指定明确的类型。在Swift中IntFloat是不能直接作运算的,必需要将他们转成同一类型才能够运算。

五、Swift抛弃了传统的++--运算,抛弃了传统的C语言式的for循环写法,而改成for-in

六、Swift的switch操做,不须要在每一个case语句结束的时候都添加break

七、Swift对enum的使用作了很大的扩展,能够支持任意类型,而OC枚举仅支持Int类型,若是要写兼容代码,要选择Int型枚举。

八、Swift代码要想被OC调用,须要在属性和方法名前面加上@objc

九、Swift独有的特性,如泛型,struct,非Int型的enum等被包含才函数参数中,即便添加@objc也不会被编译器经过。

十、Swift支持重载,OC不支持。

十一、带默认值的Swift函数再被OC调用时会自动展开。

语法检查

对于OC转Swift以后的语法变化还有不少细节值得注意,特别是对于初次使用Swift这门语言的同窗,很容易遗漏或者待着OC的思想去写代码。这里推荐一个语法检查的框架SwiftLint,能够自动化的检查咱们的代码是否符合Swift规范。

能够经过cocoapods进行引入,配置好以后,每次Build的过程,Lint脚本都会执行一遍Swift代码的语法检查操做,Lint还会将代码规范进行分级,严重的代码错误会直接报错,致使程序没法启动,不太严重的会显示代码警告(⚠️)。

若是你感受SwiftLint有点过于严格了,还能够经过修改.swiftlint.yml文件,自定义属于本身的语法规范。

文章来源:掘金 zhangferry

相关文章
相关标签/搜索