1.混编设置
新建一个桥接文件工程名-Bridging-Header.h
.到此为止,混编设置差不多就完事了.
该文件在第一次新建Swift文件时,系统会提示是否建立桥接文件,选择是即可.也可自行创建,但自行创建还需要设置路径略微麻烦.
1.1 Swift调用OC
在桥接文件中导入需要调用的OC类的头文件即可.这样就可以在Swift的类里使用OC的类了.
举例:
1 | // |
注意:对于pod管理的OC库,导入头文件时推荐使用
<>
而非""
.如#import <ZACommon/UIViewController+FRCustomNavigationBarItem.h>
,不要写为#import "UIViewController+FRCustomNavigationBarItem.h"
,否则可能导致编译通不过.
1.2 OC调用Swift
需要在OC的类里写上import "xxx-Swift.h"
.(xxx为项目名称).也可以写在PCH文件中.
如:#import "EmotionCounsel-Swift.h"
官方文档:
Migrating Your Objective-C Code to Swift
Swift-Build apps using a powerful open language.
2.混编问题
混编出现的问题大致有两类:
- 头文件引用问题
- 语法兼容性问题
2.1头文件引用问题
头文件问题可能会引起一系列的编译报错.
头文件循环引用
Swift使用了OC的某个类,该类的头文件中又想使用Swift的类.将导致Swift与OC互相引用头文件,从而引起编译错误.
解决办法:如果一定要在OC的.h文件中使用Swift的类,那就前置声明一下Swift的类名,@class swift-className
,然后在.m文件中再导入“xxx-Swift.h”头文件。
建议:在OC要调用Swift类的地方,桥接文件能写在.m文件中就不要写在.h文件.
case1
Swift中使用了OC的ZAEContactDetailViewController
,ZAEContactDetailViewController
又想使用Swift中的类.这样将会导致头文件循环引用.从而编译器会报未知类型等其他莫名的错误.
1 | import UIKit |
编译器会报错:
1 | Property with 'retain (or strong)' attribute must be of object type |
解决办法:
1 |
|
case2:OC.h文件需要遵守Swift协议,然后又需要在Swift中引用该OC类。
像上面那样前向引用,但前向引用协议后,会发现OC.h文件中会有警告。
1 |
|
办法1:屏蔽警告。没有什么太好的办法。
1 | @protocol GKPageListViewDelegate; |
办法2:用Swift给OC类添加扩展实现协议。这个肯定没警告了。
1 | extension SYANewPersonProfileBaseListViewController: GKPageListViewDelegate { |
参考:Pragma ignore missing protocol definition
case3:OC类.h文件需要使用swift文件中的枚举,然后swift类又需要使用该OC类。导致头文件循环
目前没找到什么好的办法。只能是把OC .h使用到的枚举类型改为NSInteger类型。
1 | - (instancetype)initWithStatus:(NSInteger)status; |
2.2语法兼容性问题
Swift相比OC来说是一门高级语言.Swift里面许多的高级功能是OC没有的,要想顺利混编,则必须使用二者都有的功能.所以在编写代码的时候,如果这个类可能被OC调用,那么需要避免使用Swift的特有功能或者高级语法.下面许多问题,大部分也是因为OC无法支持而导致的.
OC与Swift类的相互调用
OC不能继承Swift的类,但Swift可以继承OC的类.
要想Swift的类能在OC中可访问,则必须继承自OC的类.如果想在OC中访问Swift类的属性或方法,则需要在Swift的属性前添加@objc关键字.举个栗子:
1
2
3
4
5
6
7
8
9
10
11
12
13// 必须继承于 NSObject
class People: NSObject {
// 想公开给OC的要使用 @objc 修饰
@objc var firstName: String
@objc var lastName: String
@objc var sex: Bool = false
@objc init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
super.init()
}
}
OC中调用Swift的struct
OC不能直接调用Swift中的struct,如果想在OC中调用struct的属性,那怎么办呢?
解决办法:可以封装一个Swift的类,在类里写个方法来返回struct中的值.
OC中使用Swift的全局变量
OC不能直接使用Swift的全局变量.
解决办法:可以封装一个Swift的类,然后将全局变量作为类方法的返回值.在OC中使用该封装类.
举个栗子:有如下两个Swift的全局变量
1 | let dateFormatter: DateFormatter = { |
想在OC中使用,则需要封装一个Swift的类如下:
1 | class ZAEGlobal: NSObject { |
调用如下:
1 | NSLog(@"gNotFoundCode = %ld", (long)ZAEGlobal.gNotFoundCode); |
而Swift导入OC头文件后可以直接调用OC中定义的全局变量.
宏
Swift自身不支持宏定义.仅能在Swift中使用简单的宏.
解决办法:将原本OC中不需要接受参数的宏,定义成let常量,将需要接受参数的宏定义成函数即可.可以给我们的项目添加一个Const.swift文件,然后定义这些公共的常量和函数.由于我们的整个项目共享命名空间,我们就可以在项目内的任何地方直接使用Const.swift中定义的这些公共的常量和函数.
举个例子:#define kSeperatorLineViewHeight 0.5
这种简单的宏可以直接在Swift中使用.
但下面这种#define rgba(r,g,b,a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:a]
就不能在Swift中直接使用.需要定义为一个函数:
1 | func rgba(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) -> UIColor { |
枚举
Swift的枚举非常强大.但OC的枚举基本沿用C的枚举定义.这就意味着如果想在OC类中使用Swift的枚举,那么在编写Swift的枚举时只能使用低配版的枚举定义,这样系统才能转换成OC能支持的枚举,才能够在OC中使用.
OC使用Swift枚举
OC只能使用低配版Swift枚举.
举个例子:下面的Swift的枚举就可以在OC中使用
1 | enum ZAEPayType: Int { |
枚举ZAEPayType必须要指明原始值类型为Int,不能省略,也不能为String或其他类型.还需要加上@objc
如下的Swift枚举DateFormatterType
就不能在OC中使用:
1 | enum DateFormatterType { |
即使加上@objc
也会导致编译出错.
Swift使用OC的枚举
OC里面有两种形式定义的枚举分别为typedef NS_ENUM
,typedef NS_OPTIONS
.在Swift中使用NS_ENUM
的OC枚举与OC中无异.但NS_OPTIONS
则有些差异.
举个栗子:
1 | //聊天内容类型 |
其转换为Swift的接口如下:
1 | //聊天内容类型 |
从转换接口可以看出对于使用NS_OPTIONS
定义的OC枚举对应的Swift其实是一个结构体,
对于入参是NS_OPTIONS
定义的OC枚举,OC中的写法是YYImageCacheTypeMemory | YYImageCacheTypeDisk
,但在Swift中却是传入一个数组.let cacheType: [YYImageCacheType] = [.memory, .disk]
反之,在Swift中定义一个功能类似于NS_OPTIONS
的OC枚举,需要定义一个struct,而不是enum.
总之,在编写代码的时候,如果这个类可能被OC调用,那么需要避免使用Swift的特有功能或者高级语法,否则在OC中就用不了.
3.其他
模型解析框架。如果使用的是OC实现的模型解析框架,在Swift中使用时需要在类前加@objcMembers,要不然解析的属性值是空的。
比如下面使用的是MJExtension。SYATimelineMessageModel类前需要加@objcMembers。
ps:被@objcMembers修饰的类,会默认为类、子类、类扩展和子类扩展的所有属性和方法都加上@objc。当然如果想让某一个扩展关闭@objc,则可以用@nonobjc进行修饰。
1 | @objc enum SYATimelineMessageType: Int { |
OC,nullable标记
1 | + (NSURL *)getAvatarResourceURLWithPartial:(NSString *)partialUrl; |
上面这个方法可能会返回nil,但是没有标记为nullable。
在Swift调用时生成了一个nil的URL,虽然SD的方法url可以传nil,但还是崩溃了。
1 | avatarImageView.sd_setImage(with: NSURL.getAvatarResourceURL(withPartial: model.avatar), placeholderImage: UIImage.lightGrayPlaceholder()) |
这说明Swift-OC桥接时nil检查很严格,OC没标记为nullable,那么Swift认为不会有空对象,然而如果实际OC返回了空对象那么桥接就会崩溃。
修复:添加nullable。
1 | + (nullable NSURL *)getAvatarResourceURLWithPartial:(NSString *)partialUrl; |