与Objective-C Swift代码兼容
(本文最初是用俄语撰写的,并在此处发布。)
尽管Apple已向我们提供了有关如何在Objective-C应用程序中使用Swift代码的详细文档(反之亦然),但在此方面还远远不够。 当我需要为Swift框架提供与Objective-C的兼容性时,Apple文档提出了更多问题,然后给出了答案(或者至少留有很多空白)。 密集的搜索证明该主题的照明效果很差:有关StackOverflow的几个问题和一些介绍性文章–这就是我所发现的全部。
本出版物是对找到的信息和我自己的经验的概括。 所描述的所有方法并不能声称是一个很好的实践,它们只是提供了一种解决问题的方法。
TL; DR。 为了在Objective-C中使用Swift代码,必须牺牲一些Swift功能,并为原始的Swift代码编写一个包装程序,该代码将不使用不兼容的功能(例如结构,泛型,枚举关联值,协议扩展等)。 所有包装器类都必须继承NSObject。
开始
因此,我们有一个基于Objective-C的项目以及一些想要在其中使用的Swift代码模块。 例如,它可以是在CocoaPods的帮助下添加的Swift框架。 像往常一样,我们向Podfile添加依赖项,运行pod install
,打开xcworkspace
文件。
要使Swift框架可见,就不需要使用整个模块(就像我们在Swift中所做的那样)或单个文件(就像我们在Objective-C中所做的那样)导入。 必须导入的是一个名为-Swift.h
的文件-这是自动生成的头文件,该文件是Objective-C代码与Swift公共API之间的连接链接。 看起来像这样:
#import“ YourProjectName-Swift.h”
在Objective-C中使用Swift类
如果您可以立即使用Objective-C中的某些Swift类或方法,那么您很幸运:有人为您解决了兼容性问题。 事实是,Objective-C仅消化NSObject继承者和属性的公共API,初始化程序和方法必须由@objc
属性标记。
处理自己的代码时,您始终可以继承所需的任何内容并添加任何属性。 当然,在这种情况下,您甚至可以用Objective-C编写,对吗? 因此,最好专注于他人的代码。 我们可以做什么? 写包装纸。
例如,考虑以下Swift类:
公共类SwiftClass {
公共功能swiftMethod(){
//此处执行。
}
}
我们创建自己的Swift文件,导入外部Swift模块,创建NSObject
继承类,并在该类内部创建想要的Swift类型的私有属性。 最主要的是一个通过属性调用原始Swift类型方法的方法:
进口基金会
导入SwiftFramework
公共类SwiftClassObjCWrapper:NSObject {
私人让swiftClass = SwiftClass()
@objc
公共功能swiftMethod(){
swiftClass.swiftMethod()
}
}
( NSObject
和@objc
属性都可以从Foundation获得。)
显然,我们不能使用相同的名称。 但是我们可以使用原始名称将包装器API公开给Objective-C:
@objc(SwiftClass)
公共类SwiftClassObjCWrapper:NSObject {/ * ... * /}
现在,Objective-C调用看起来像原始的Swift类调用:
SwiftClass * swiftClass = [SwiftClass新];
[swiftClass swiftMethod];
在Obective-C中使用Swift方法
不幸的是,我们不能通过@objc
标记任何公共方法,而只能在Objective-C中使用它。 我们正在处理不同的编程语言,这些编程语言通常具有不同的机会,有时实现不同的逻辑,并且在Objective-C中显然缺少一些Swift功能。
例如,必须忘记默认参数值。 该方法:
@objc
public func anotherSwiftMethod(参数:Int = 1){
//此处执行。
}
…将在Objective-C中看起来像这样:
[swiftClassObject anotherSwiftMethodWithParameter:1];
(这里1
只是一个传递的值,默认值不存在。)
方法名称
语言的互操作性具有其自己的桥接名称系统。 在大多数情况下,它令人满意地工作,但有时需要进行干预才能使其可读。 例如, do(thing:)
将变成doWithThing:
这破坏了想要的命名逻辑)。 @objc
再次解救:
@objc(doThing :)
公共功能(类型:) {
//此处执行。
}
引发异常的方法
投掷方法(而不是throws
功能)具有一个额外的参数-桥接到原始Error
NSError
指针:
@objc(doThing:error :)
公共功能do(thing:Type)throw {
//此处执行。
}
方法用法看起来很熟悉:
NSError *错误=无;
[swiftClassObject doThing:thingValue error:&error];
如果(错误){
//处理错误。
}
Swift类型作为参数和返回值
如果method具有非桥接Swift类型的参数或返回了它,那么再次进行Objective-C就无法使用它,而无需进行其他工作。 考虑原始代码:
公共类SwiftClass {
公共功能swiftMethod(){/ * ... * /}
}
公共类AnotherSwiftClass {
公共功能anotherSwiftMethod()-> SwiftClass {
返回SwiftClass()
}
}
合格包装:
@objc(SwiftClass)
公共类SwiftClassObjCWrapper:NSObject {
私人让swiftClassObject:SwiftClass
//不需要Objective-C曝光。
公共初始化(swiftClassObject:SwiftClass){
self.swiftClassObject = swiftClassObject
super.init()
}
@objc
公共功能swiftMethod(){
swiftClassObject.swiftMethod()
}
}
@objc(AnotherSwiftClass)
公共类AnotherSwiftClassWrapper:NSObject {
私人让anotherSwiftClassObject = AnotherSwiftClass()
@objc
公共功能anotherSwiftMethod()-> SwiftClassObjCWrapper {
返回SwiftClassObjCWrapper(swiftClassObject:anotherSwiftClassObject.anotherSwiftMethod())
}
}
和用法:
AnotherSwiftClass * anotherSwiftClassObject
= [AnotherSwiftClass new];
SwiftClass * swiftClassObject
= [anotherSwiftClassObject anotherSwiftMethod];
[swiftClassObject swiftMethod];
在Objective-C中使用Swift协议
公开不使用非桥接Swift类型的Swift类协议是没有问题的。 考虑一个相反的:
公共类SwiftClass {/ * ... * /}
公共协议SwiftProtocol {
func swiftProtocolMethod()-> SwiftClass
}
公共函数swiftMethod(swiftProtocolObject:SwiftProtocol){
//此处执行。
}
这种情况下的类包装器:
@objc(SwiftClass)
公共类SwiftClassObjCWrapper:NSObject {
让swiftClassObject = SwiftClass()
}
“包装”协议:
@objc(SwiftProtocol)
公用协议SwiftProtocolObjCWrapper:类{
func swiftProtocolMethod()-> SwiftClassObjCWrapper
}
(一个人只能使用类协议, NSObjectProtocol
继承人和受NSObject
约束的协议。)
最有趣的地方是:让我们声明符合原始协议的Swift类-这就像是原始协议与“包装”协议之间的桥梁:
class SwiftProtocolWrapper:SwiftProtocol {
私人让swiftProtocolObject:SwiftProtocolObjCWrapper
init(swiftProtocolObject:SwiftProtocolObjCWrapper){
self.swiftProtocolObject = swiftProtocolObject
}
func swiftProtocolMethod()-> SwiftClass {
返回swiftProtocolObject
.swiftProtocolMethod()
.swiftClassObject
}
}
不幸的是,使用原始协议的方法必须包装为:
@objc
公共函数swiftMethodWith(swiftProtocolObject:SwiftProtocolObjCWrapper){
让swiftProtocolObject = SwiftProtocolWrapper(swiftProtocolObject:swiftProtocolObject)
methodOwnerObject.swiftMethodWith(swiftProtocolObject:swiftProtocolObject)
}
不是很明显的连锁店,对不对? 但是,如果类型和协议具有大量方法,则包装代码不会出现在如此不成比例的卷中。
严格来说,符合Objective-C协议很干净:
@interface ObjectiveCClass:NSObject
@结束
@实现ObjectiveCClass
-(SwiftClass *)swiftProtocolMethod {
返回[SwiftClass new];
}
@结束
及其用法:
(ObjectiveCClass *)objectiveCClassObject = [ObjectiveCClass new];
[methodOwnerObject swiftMethodWithSwiftProtocolObject:objectiveCClassObject];
在Objective-C中使用Swift枚举
在Objective-C中使用Swift enum
,需要满足的唯一条件是它们必须具有整数原始类型。 之后,可以添加@objc
属性。
当我们无法修改现有的enum
,可以像往常一样包装它并使用它的方法:
枚举SwiftEnum {
案例优先
案例二
}
SwiftClass类{
func swiftMethod()-> SwiftEnum {
//此处执行。
}
}
@objc(SwiftEnum)枚举SwiftEnumObjCWrapper:Int {
案例优先
案例二
}
@objc(SwiftClass)
公共类SwiftClassObjCWrapper:NSObject {
让swiftClassObject = SwiftClass()
@objc
公共功能swiftMethod()-> SwiftEnumObjCWrapper {
切换swiftClassObject.swiftMethod(){
case .firstCase:返回.firstCase
案例.secondCase:返回.secondCase
}
}
}
结论
这就是我今天讨论的全部内容。 当然,将Swift代码集成到Objective-C方面还有更多方面,但是我确信所有这些方面都可以借助上述逻辑来处理。
另一方面,这种方法无疑存在缺陷。 显而易见的例子是,必须编写一些额外的代码,通常是很多代码。 另一个缺陷是,Swift代码被有效地转移到了Objective-C运行时中,并且在工作时有一些开销,运行速度稍慢。 但是,在许多情况下,这种减速是无法察觉的,尤其是在快速的现代设备上。 但是,必须记住,解决方法始终是一种解决方法。
并感谢您的阅读!
如果您想关注我,这是我的Twitter。