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 #import "AppDelegate.h" #import "ZAEGradientView.h" #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 UIKitclass 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; @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; @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 #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的类继承自NSObject,也添加了@objc,那也不能),但Swift可以继承OC的类.
要想Swift的类能在OC中可访问,则必须继承自OC的类.如果想在OC中访问Swift类的属性或方法,则需要在Swift的属性前添加@objc关键字.举个栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 class People : NSObject { @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 } public struct YYImageCacheType : OptionSet { public init (rawValue : UInt ) public static var memory: YYImageCacheType { get } public static var disk: YYImageCacheType { get } 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 internal var news_type: SYATimelineMessageType = .comment internal var user_info: SYAUserInfo ? internal var moment: SYATimelineItem ? internal var comments: SYATimelineCommentItem ? internal var like: SYATimelineLikeItem ? 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;
bug Xcode 16.1对OC类生成Swift接口,生成的API只有几个,明明有很多个的,原因不明。但是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 #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface XQNavigationBarView : UIView @property (nonatomic , strong , readonly ) UIButton *backButton;@property (nonatomic , strong , nullable ) UIView *titleView;@property (nonatomic , copy , nullable ) NSString *title;@property (nonatomic , strong ) UIFont *titleFont;@property (nonatomic , strong ) UIColor *titleColor;@property (nonatomic , copy , nullable ) NSAttributedString *attributeTitle;@property (nonatomic , copy , nullable ) void (^backActionBlock)(void );+ (instancetype )navigationBar; @end NS_ASSUME_NONNULL_END
生成的API:
1 2 3 4 5 6 open class XQNavigationBarView { open var backActionBlock: (() -> Void )? open class func navigationBar () -> Self ! }
参考 Objective-C Swift 混编的模块二进制化 1:��础知识