Swift 项目兼容 Objective-C 问题汇总
Posted 唐巧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 项目兼容 Objective-C 问题汇总相关的知识,希望对你有一定的参考价值。
版权申明:作者 已将本文在微信公众平台的发表权「独家代理」给 ios 开发(iOSDevTips)微信公共帐号。本文的所有打赏归一叶所有。
作者介绍:一叶,四年 iOS 开发,曾就职盛大文学,现工作于奇点国际,热爱移动互联网,内存分析及性能优化经验丰富,擅长重构、接口设计、框架搭建,欢迎访问博客
一、解决问题
Swift 项目需要使用封装好的 Objective-c 组件、第三方类库,苹果提供的解决方案能够处理日常大部分需求,但还不能称之为完美,混编过程中会遇到很多问题。本文将 Swift 兼容 Objective-c 的问题汇总,以帮助大家更好的使用 Swift,内容列表如下:
1. Swift 调用 Objective-c 代码
2. Objective-c 调用 Swift 代码
3. Swift 兼容 Xib/Storyboard
4. Objective-c 巧妙调用不兼容的 Swift 方法
5. 多 Target 编译错误解决
6. 第三方类库支持
二、基础混合编程
Swift 与 Objective-c 的代码相互调用,并不像 Objective-c 与 C/C++ 那样方便,需要做一些额外的配置工作。无论是 Swift 调用 Objective-c 还是 Objective-c 调用 Swift,Xcode 在处理上都需要两个步骤:
2.1 Swift 调用 Objective-c 代码
Xcode 对于 Swift 调用 Objective-c 代码,除宏定义外,其它支持相对完善。
2.1.1 使用 Objetvie-c 的第一步,
告诉 Xcode、哪些 Objective-c 类要使用,新建 .h 头文件,文件名可以任意取,建议采用 “ 项目名-Bridging-Header.h” 命令格式。
Tips
Swift 之 IOS 项目,在 Xcode6 创建类文件,默认会自动选择 OS X 标签下的文件,这时 一定要选择 iOS 标签 下的文件,否则会出现语法智能提示不起作用,严重时会导致打包出错。
2.1.2 第二步,Target 配置,使创建的头文件生效
设置 Objective-C Bridging Header 时,路径要配置正确,例如:创建的名为 “ILSwift-Bridging-Header.h” 文件,存于 ILSwift 项目文件夹的根目录下,写法如下:
ILSwift/ILSwift-Bridging-Header.h
当然,在新项目中,直接创建一个 Objective-c 类,Xcode 会提示:
直接选择 Yes 即可,如果不小心点了其它按钮,可以按照上面的步骤一步一步添加。
2.2 Objective-c 调用 Swift 代码
2.2.1 Objective-c 调用 Swift 代码两个步骤
第一步告诉 Xcode 哪些类需要使用 (继承自 NSObject 的类自动处理,不需要此步骤),通过关键字 @objc(className) 来标记
import UIKit@objc(ILWriteBySwift)class ILWriteBySwift {
var name: String! class func newInstance() -> ILWriteBySwift {
return ILWriteBySwift()
}
}
第二步引入头文件,Xcode 头文件的命名规则为
$(SWIFT_MODULE_NAME)-Swift.h
示例如下:
#import "ILSwift-Swift.h"
Tips
不清楚 SWIFT_MODULE_NAME 可通过以下步骤查看
2.2.2 找不到 $(SWIFT_MODULE_NAME)-Swift.h
遇到此问题可按以下步骤做常规性检查
确定导入 SWIFT_MODULE_NAME)-Swift.h 头文件的文件名正确
2.SWIFT_MODULE_NAME)-Swift.h 在 clean 后没有重新构建,执行 Xcode->Product->Build头文件循环
在混合编程的项目中,由于两种语言的同时使用,经常会出现以下需求:在 Swift 项目中需要使用 Objectvie-c 写的 A 类,而 A 类又会用到 Swift 的一些功能,头文件的循环,导致编译器不能正确构建 $(SWIFT_MODULE_NAME)-Swift.h,遇到此问题时,在 .h 文件做如下处理
// 删除以下头文件
//#import "ILSwift-Swift.h"
// 通过代码导入类
@class ILSwiftBean;
在 Objevtive-c 的 .m 文件最上面,添加
#import "ILSwift-Swift.h"
出现 Use of undecalared identifier 错误或者找不到方法,如下:
引起的原因有以下几种可能:
1. 使用的 Swift 类不是继承自 NSObject,加入关键字即可
2.SWIFT_MODULE_NAME)-Swift.h 没有实时更新,Xcode->Product->Build
3. 此 Swift 文件中使用了 Objective-c 不支持的类型或者语法,如 private
出现 部分方法找不到 的问题,Xcode 无智能提示:
此方法使用了 Objective-c 不支持的类型或者语法
苹果官方给出的不支持转换的类型
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
三、Xib/StoryBoard 支持
Swift 项目在使用 Xib/StoryBoard 时,会遇到两种不同的问题
1.Xib:不加载视图内容
2.Storyboard:找不到类文件
3.1 Xib 不加载视图内容
在创建 UIViewController 时,默认选中 Xib 文件,在 Xib 与类文件名一致时,可通过以下代码实例化:
let controller = ILViewController()
运行,界面上空无一物,Xib 没有被加载。解决办法,在类的前面加上 @objc(类名),例如:
import UIKit@objc(ILViewController)class ILViewController: UIViewController {}
Tips:
StoryBoard 中创建的 UIViewController,不需要 @objc(类名) 也能够保持兼容
3.2 Storyboard 找不到类文件
Swift 语言引入了 Module 概念,在通过关键字 @objc(类名) 做转换的时候,由于 Storboard 没有及时更新 Module 属性,会导致如下两种类型错误:
3.2.1 用 @objc(类名) 标记的 Swift 类或者 Objective-c 类可能出现错误:
2015-06-02 11:27:42.626 ILSwift[2431:379047] Unknown class _TtC7ILSwift33ILNotFindSwiftTagByObjcController in Interface Builder file.
解决办法,按下图,选中 Module 中的空白,直接回车
3.2.2 无 @objc(类名) 标记的 Swift 类
2015-06-02 11:36:29.788 ILSwift[2719:417490] Unknown class ILNotFindSwiftController in Interface Builder file.
解决办法,按下图,选择正确的 Module
产生上面错误的原因:
在设置好 Storyboard 后,直接在类文件中,添加或者删除 @objc(类名) 关键字,导致 Storyboard 中 Module 属性没有自动更新,所以一个更通用的解决办法是,让 Storyboard 自动更新 Module, 如下:
3.3 错误模拟 Demo 下载
为了能够让大家更清楚的了解解决流程,将上面的错误进行了模拟,想动手尝试解决以上问题的同学可以直接下载
四、Objective-c 巧妙调用不兼容的 Swift 方法
在 Objective-c 中调用 Swift 类中的方法时,由于部分 Swift 语法不支持转换,会遇到无法找到对应方法的情况,如下:
import UIKitenum HTTPState { case Succed, Failed, NetworkError, ServerError, Others}class ILHTTPRequest: NSObject {
class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) {
dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void in
NSThread.sleepForTimeInterval(3)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
callback(state: HTTPState.Succed)
})
})
}
}
对应的 $(SWIFT_MODULE_NAME)-Swift.h 文件为:
SWIFT_CLASS("_TtC12ILSwiftTests13ILHTTPRequest")@interface ILHTTPRequest : NSObject- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;@end
从上面的头文件中可以看出,方法 requestLogin 使用了不支持的 Swift 枚举,转换时方法被自动忽略掉,有以下两种办法,可以巧妙解决类似问题:
4.1 用支持的 Swift 语法包装
在 Swift 文件中,添加一个可兼容包装方法 wrapRequestLogin, 注意此方法中不能使用不兼容的类型或者语法
import UIKitenum HTTPState: Int { case Succed = 0, Failed = 1, NetworkError = 2, ServerError = 3, Others = 4}class ILHTTPRequest: NSObject {
class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) {
dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void in
NSThread.sleepForTimeInterval(3)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
callback(state: HTTPState.Succed)
})
})
} class func wrapRequestLogin(userName: String, password: String, callback: (state: Int) -> (Void)) {
self.requestLogin(userName, password: password) { (state) -> (Void) in
callback(state: state.rawValue)
}
}
}
对应的 $(SWIFT_MODULE_NAME)-Swift.h 文件为:
SWIFT_CLASS("_TtC12ILSwiftTests13ILHTTPRequest")@interface ILHTTPRequest : NSObject+ (void)wrapRequestLogin:(NSString * __nonnull)userName password:(NSString * __nonnull)password callback:(void (^ __nonnull)(NSInteger))callback;
- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;@end
此时,我们可以在 Objective-c 中直接使用包装后的方法 wrapRequestLogin
4.2 巧妙使用继承
使用继承可以支持所有的 Swift 类型,主要的功能在 Objective-c 中实现,不支持的语法在 Swift 文件中调用,例如,ILLoginSuperController 做为父类
@interface ILLoginSuperController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *userNameField;
@property (weak, nonatomic) IBOutlet UITextField *passwordField;
- (IBAction)loginButtonPressed:(id)sender;
@end
////////////////////////////////////////////////////////////////
@implementation ILLoginSuperController
- (IBAction)loginButtonPressed:(id)sender
{
}
@end
创建 Swift 文件,继承自 ILLoginSuperController,在此 Swift 文件中调用那些不支持的语法
import UIKit
class ILLoginController: ILLoginSuperController {
override func loginButtonPressed(sender: AnyObject!) {
ILHTTPRequest.requestLogin(self.userNameField.text, password: self.passwordField.text) { (state) -> (Void) in
// 具体业务逻辑
}
}
}
五、多 Target 编译错误解决
在使用多 Target 时,会出现一些编译错误
5.1 Use of undeclared type
此类错误,是因为当前运行的 Target 找不到必须编译文件。将文件添加到 Target 即可,如下支持 ILSwiftTests Target,选中 ILSwiftTests 前的复选框即可
5.2 does not have a member named
此类错误可能由于如下两种原因引起,解决办法同上:
1. 此方法来自父类,父类文件没有加入到当前 Target
2. 此方法来自扩展,扩展没有加入到当前 Target
Tips
如果检查发现,所有的类文件都已经准确添加到 Target 中,但编译还是不通过,此时着重检查桥接文件是否正确设置,是否将相应的头文件加入到了桥接文件中。如无特别要求,建议将所有 Target 的桥接文件全都指向同一文件。关于桥接文件的设置,请参考 2.1
六、第三方类库支持
Swift 项目取消了预编译文件,一些第三方 Objective-c 库没有导入必要框架 (如 UIKit) 引起编译错误
6.1 Cocoapods 找不到 .o 文件
在使用了 Cocoapods 项目中,会出现部分类库的 .o 文件找不到,导致此种错误主要是以下两种问题:
1. 类库本身存在编译错误
2.Swift 没有预编译,UIKit 等没有导入
将此库文件中的代码文件直接加到项目中,编译,解决错误
6.2 JSONModel 支持
在 Swift 中可以使用 JSONModel 部分简单功能,一些复杂的数据模型建议使用 Objevtive-c
import UIKit@objc(ILLoginBean)
public class ILLoginBean: JSONModel {
var userAvatarURL: NSString?
var userPhone: NSString!
var uid: NSString!
}
Tips
在 Swift 使用 JSONModel 框架时,字段只能是 NSFoundation 中的支持类型,Swift 下新添加的 String、Int、Array 等都不能使用
6.3 友盟统计
Swift 项目中引入友盟统计 SDK 会出现 referenced from 错误:
解决办法,找到 Other Linker Flags,添加 -lz
七、综述
现在大部分成熟的第三方框架都是使用 Objective-c 写的,开发时不可避免的涉及到两种语言的混合编程,期间会遇到很多奇怪的问题。因为未知才有探索的价值,Swift 的简洁快速,能够极大的推进开发进度。所以从今天开始,大胆的开始尝试。
以上是关于Swift 项目兼容 Objective-C 问题汇总的主要内容,如果未能解决你的问题,请参考以下文章
Swift与Objective-C的兼容“黑魔法”:@objc和Dynamic
Swift互用性:与 Objective-C 的 API 交互(Swift 2.0版更新)-备