文档章节

OC项目转Swift指南

o
 osc_p6dyctjv
发布于 07/12 11:43
字数 3405
阅读 42
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

运行环境:Xcode 11.1 Swift5.0

最近参与的一个项目需要从Objective-C(以下简称OC)转到Swift,期间遇到了一些坑,于是有了这篇总结性的文档。
如果你也有将OC项目Swift化的需求,可以作为参考。

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

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

自动化工具

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

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

手动Swift化

桥接文件

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

这个桥接文件的作用就是供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文件中的引用下文有介绍。

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开发学习资料、面试真题,进群即可获取!


点击此处,立即与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 < fileprivate < internal < public < 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 < AnyObject < Any

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

1、Swift语句中不需要加分号;

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

3、Swift类内一般不需要写self,但是闭包内是需要写的。

4、Swift是强类型语言,必须要指定明确的类型。在Swift中IntFloat是不能直接做运算的,必须要将他们转成同一类型才可以运算。

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

6、Swift的switch操作,不需要在每个case语句结束的时候都添加break

7、Swift对enum的使用做了很大的扩展,可以支持任意类型,而OC枚举仅支持Int类型,如果要写兼容代码,要选择Int型枚举。

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

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

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

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

语法检查

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

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

如果你感觉SwiftLint有点过于严格了,还可以通过修改.swiftlint.yml文件,自定义属于自己的语法规范。

文章来源:掘金 zhangferry

o
粉丝 0
博文 71
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Swift百万线程攻破单例(Singleton)模式

一、不安全的单例实现 在上一篇文章我们给出了单例的设计模式,直接给出了线程安全的实现方法。单例的实现有多种方法,如下面: class SwiftSingleton { } 这段代码的实现,在shared中进行条...

一叶博客
2014/06/20
3.4K
16
5分钟 maven3 快速入门指南

前提条件 你首先需要了解如何在电脑上安装软件。如果你不知道如何做到这一点,请询问你办公室,学校里的人,或花钱找人来解释这个给你。 不建议给Maven的服务邮箱来发邮件寻求支持。 安装Mav...

fanl1982
2014/01/23
1.2W
7
php开源框架--CorePHP

简介: CorePHP框架是一个快速,安全,灵活的php开源框架,主要是为了简化和快速开发小型项目和开源系统二次开发而诞生。它既可以完美的支持MVC模式,又可以不受限制的支持传统编程模式。它是...

shooke
2012/12/27
2.8K
1
Sar数据转HTML--Sar2html

Sar2html 可以将 sar 程序执行的二进制结果数据转成图形的 HTML 格式,它提供了命令行工具、Web 接口和数据收集脚本。使用 sar2ascii 可从服务器 (HP-UX 11.11, 11.23, and 11,31, Redhat 3...

匿名
2013/01/10
1.2K
0
开源物理项目--OSP

OSP (OpenSource Physics)开源物理项目是一个完整的 Java 应用套件用来模拟不同的物理系统,覆盖天文学、电磁学、经典力学、量子力学、光学和相对论。 下载后只需要简单运行 java -jar file...

匿名
2013/04/23
1.8K
0

没有更多内容

加载失败,请刷新页面

加载更多

如何设置HTML的默认值 元件? - How can I set the default value for an HTML element?

问题: I thought that adding a "value" attribute set on the <select> element below would cause the <option> containing my provided "value" to be selected by default: 我认为在下面......

富含淀粉
23分钟前
20
0
CSS--select消除点击背景色

1. IOS(苹果)系统手机上触发点击事件会有高亮的阴影,解决办法如下: *{ -webkit-tap-highlight-color: rgba(0,0,0,0);-webkit-tap-highlight-color: transparent; } 2. Android(安卓)...

xrshop
28分钟前
20
0
相约周四晚8点 都说今年秋招难,来直播听参加过提前批的同学怎么说

说到每年的秋招,相信大家都知道它分“提前批”和“正式批”。 提前批的要求一般来说比正式批更高,难度也更大,是各个公司企业间的“人才战”,但sp/ssp相比正式批较多,而且就算没有通过也...

空白贝塔
今天
0
0
数据分析神器!好用,盘它!

在职场中,有些人总是手忙脚乱,看起来忙得飞起,实际上工作却杂乱无章,做完还要检查,甚至返工。 但也有一些人,游刃有余,喝着奶茶就把工作搞定了,从来不加班。 这背后是扎心的职场真相:...

BI佐罗
今天
0
0
Kubernetes Service与Endpoint指向外部服务

--------------------------------------------------kubectl apply -f - <<EOFkind: ServiceapiVersion: v1metadata: name: snake-demo-svc namespace: basespec: ports: - po......

_snake_
33分钟前
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部