0%

OC与Swift混编

1.混编设置

新建一个桥接文件工程名-Bridging-Header.h.到此为止,混编设置差不多就完事了.
该文件在第一次新建Swift文件时,系统会提示是否建立桥接文件,选择是即可.也可自行创建,但自行创建还需要设置路径略微麻烦.

1.1 Swift调用OC

在桥接文件中导入需要调用的OC类的头文件即可.这样就可以在Swift的类里使用OC的类了.

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

//在Swift里调用OC,需要在桥接文件里导入OC头文件
//调用自身module里的OC代码
#import "AppDelegate.h"
#import "ZAEGradientView.h"

//Pod管理的OC库
#import <MJRefresh/MJRefresh.h>
#import <YYModel/YYModel.h>
#import <ZACommon/UIViewController+FRCustomNavigationBarItem.h>
#import <ZACommon/InnerBandCore.h>

注意:对于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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import UIKit

class ZAEDiscoverViewController: ZAEViewController {

override func viewDidLoad() {
super.viewDidLoad()

}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let detailVC = ZAEContactDetailViewController.init()
navigationController?.pushViewController(detailVC, animated: true)
}

}

...

#import "ZAEViewController.h"

NS_ASSUME_NONNULL_BEGIN

@interface ZAEContactDetailViewController : ZAEViewController

@property (nonatomic, strong) People *people; //People为swift的类

@end

NS_ASSUME_NONNULL_END

编译器会报错:

1
2
Property with 'retain (or strong)' attribute must be of object type
Unknown type name 'People'

解决办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import "ZAEViewController.h"

NS_ASSUME_NONNULL_BEGIN

@class People; //使用@class 前置声明一下即可.

@interface ZAEContactDetailViewController : ZAEViewController

@property (nonatomic, strong) People *people; //People为swift的类

@end

NS_ASSUME_NONNULL_END

case2:OC.h文件需要遵守Swift协议,然后又需要在Swift中引用该OC类。

像上面那样前向引用,但前向引用协议后,会发现OC.h文件中会有警告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol GKPageListViewDelegate;

@interface SYANewPersonProfileBaseListViewController : UIViewController <GKPageListViewDelegate> //警告:cannot find protocol definition for 'GKPageListViewDelegate'

@property (nonatomic, copy, nullable) void(^scrollCallBack)(UIScrollView *scrollView);

@property (nonatomic, assign) NSInteger userId;

@property (nonatomic, strong, nullable) SYAUserInfo *userInfo;

- (void)requestPhotoAlbum;

@end

NS_ASSUME_NONNULL_END

办法1:屏蔽警告。没有什么太好的办法。

1
2
3
4
5
6
7
@protocol GKPageListViewDelegate;

#pragma clang diagnostic push
// To get rid of 'No protocol definition found' warnings which are not accurate
#pragma clang diagnostic ignored "-Weverything"
@interface SYANewPersonProfileBaseListViewController : UIViewController <GKPageListViewDelegate>
#pragma clang diagnostic pop

办法2:用Swift给OC类添加扩展实现协议。这个肯定没警告了。

1
2
3
4
5
6
7
8
9
10
11
12
13
extension SYANewPersonProfileBaseListViewController: GKPageListViewDelegate {
public func listScrollView() -> UIScrollView {
fatalError("Must be implemented by Concrete subclass.")
}

public func listViewDidScroll(callBack: @escaping (UIScrollView) -> ()) {
self.scrollCallBack = callBack
}

public func listView() -> UIView {
return self.view
}
}

参考: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
2
3
4
5
6
7
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()

let notFoundCode = 404

想在OC中使用,则需要封装一个Swift的类如下:

1
2
3
4
class ZAEGlobal: NSObject {
@objc static let gNotFoundCode = notFoundCode
@objc static let gDateFormatter = dateFormatter
}

调用如下:

1
2
NSLog(@"gNotFoundCode = %ld", (long)ZAEGlobal.gNotFoundCode);  
NSLog(@"日期:%@", [ZAEGlobal.gDateFormatter stringFromDate:NSDate.date]);

而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
2
3
func rgba(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) -> UIColor {
return UIColor.init(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: a)
}

枚举

Swift的枚举非常强大.但OC的枚举基本沿用C的枚举定义.这就意味着如果想在OC类中使用Swift的枚举,那么在编写Swift的枚举时只能使用低配版的枚举定义,这样系统才能转换成OC能支持的枚举,才能够在OC中使用.

OC使用Swift枚举

OC只能使用低配版Swift枚举.

举个例子:下面的Swift的枚举就可以在OC中使用

1
2
3
4
5
6
7
@objc enum ZAEPayType: Int {
//全款付清
case fullPayment = 1

//分期支付
case instalmentPayment
}

枚举ZAEPayType必须要指明原始值类型为Int,不能省略,也不能为String或其他类型.还需要加上@objc

如下的Swift枚举DateFormatterType就不能在OC中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum DateFormatterType {
case formatterType1
case formatterType2
case formatterType3

var formatterTypeString: String {
switch self {
case .formatterType1:
return "yyyy-MM-dd HH:mm:ss"
case .formatterType2:
return "yyyy-MM-dd"
case .formatterType3:
return "yyyy.MM.dd"
}
}
}

即使加上@objc也会导致编译出错.

Swift使用OC的枚举

OC里面有两种形式定义的枚举分别为typedef NS_ENUM,typedef NS_OPTIONS.在Swift中使用NS_ENUM的OC枚举与OC中无异.但NS_OPTIONS则有些差异.

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//聊天内容类型
typedef NS_ENUM(NSInteger, ZAEMsgChatContentType) {
ZAEMsgChatContentTypeUnknow = 0, //未知
ZAEMsgChatContentTypePlainText, //纯文本消息
ZAEMsgChatContentTypePicture, //图片消息
ZAEMsgChatContentTypeVoice, //语音消息
ZAEMsgChatContentTypeVideo, //视频消息
};

/// Image cache type
typedef NS_OPTIONS(NSUInteger, YYImageCacheType) {
/// No value.
YYImageCacheTypeNone = 0,

/// Get/store image with memory cache.
YYImageCacheTypeMemory = 1 << 0,

/// Get/store image with disk cache.
YYImageCacheTypeDisk = 1 << 1,

/// Get/store image with both memory cache and disk cache.
YYImageCacheTypeAll = YYImageCacheTypeMemory | YYImageCacheTypeDisk,
};

其转换为Swift的接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//聊天内容类型
public enum ZAEMsgChatContentType : Int {


case unknow //未知

case plainText //纯文本消息

case picture //图片消息

case voice //语音消息

case video //视频消息
}

/// Image cache type
public struct YYImageCacheType : OptionSet {

public init(rawValue: UInt)


/// Get/store image with memory cache.
public static var memory: YYImageCacheType { get }


/// Get/store image with disk cache.
public static var disk: YYImageCacheType { get }


/// Get/store image with both memory cache and disk cache.
public static var all: YYImageCacheType { get }
}

从转换接口可以看出对于使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@objc enum SYATimelineMessageType: Int {
case comment = 1
case reply
case praise
}

@objcMembers class SYATimelineMessageModel: NSObject {
internal var avatar: String {
if let v = user_info?.photo {
return v
}
return ""
}
internal var nickname: String {
if let v = user_info?.nickname {
return v
}
return ""
}

internal var news_id: Int = 0
/// 新消息类型 1:新评论 2:新回复 3:新点赞
internal var news_type: SYATimelineMessageType = .comment
internal var user_info: SYAUserInfo?
internal var moment: SYATimelineItem?
internal var comments: SYATimelineCommentItem?
internal var like: SYATimelineLikeItem?

// MARK: - 本地字段
internal var cellHeight: CGFloat = 0
}

OC,nullable标记

1
+ (NSURL *)getAvatarResourceURLWithPartial:(NSString *)partialUrl;

上面这个方法可能会返回nil,但是没有标记为nullable。

在Swift调用时生成了一个nil的URL,虽然SD的方法url可以传nil,但还是崩溃了。

1
2
3
4
5
6
avatarImageView.sd_setImage(with: NSURL.getAvatarResourceURL(withPartial: model.avatar), placeholderImage: UIImage.lightGrayPlaceholder())

- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;

这说明Swift-OC桥接时nil检查很严格,OC没标记为nullable,那么Swift认为不会有空对象,然而如果实际OC返回了空对象那么桥接就会崩溃。

修复:添加nullable。

1
+ (nullable NSURL *)getAvatarResourceURLWithPartial:(NSString *)partialUrl;

参考

Objective-C Swift 混编的模块二进制化 1:��础知识

觉得文章有帮助可以打赏一下哦!