如何创建符合 Swift 和 Objective-C 之间共享协议的类方法?
Posted
技术标签:
【中文标题】如何创建符合 Swift 和 Objective-C 之间共享协议的类方法?【英文标题】:How to create class methods that conform to a protocol shared between Swift and Objective-C? 【发布时间】:2015-06-06 15:15:50 【问题描述】:我最近一直在学习 Swift。
我决定编写一个混合 Swift/Objective-C 应用程序,该应用程序使用两种语言实现的相同算法来执行计算密集型任务。
程序计算大量素数。
我定义了一个计算对象的 Swift 和 Objective-C 版本都应该遵守的协议。
对象都是单例,所以我在Objective-C中创建了一个典型的单例访问方法:
+ (NSObject <CalcPrimesProtocol> *) sharedInstance;
整个协议如下所示:
#import <Foundation/Foundation.h>
@class ComputeRecord;
typedef void (^updateDisplayBlock)(void);
typedef void (^calcPrimesCompletionBlock)(void);
@protocol CalcPrimesProtocol <NSObject>
- (void) calcPrimesWithComputeRecord: (ComputeRecord *) aComputeRecord
withUpdateDisplayBlock: (updateDisplayBlock) theUpdateDisplayBlock
andCompletionBlock: (calcPrimesCompletionBlock) theCalcPrimesCompletionBlock;
@optional //Without this @optional line, the build fails.
+ (NSObject <CalcPrimesProtocol> *) sharedInstance;
@end
Objective-C 版本的类实现了上面定义的方法,不用担心。
swift版有个方法:
class func sharedInstance() -> CalcPrimesProtocol
但是,如果我将该方法设为协议的必需方法,则会收到编译器错误“类型”CalcPrimesSwift 不符合协议“CalcPrimesProtocol”。
但是,如果我在协议中将单例类方法 sharedInstance 标记为可选,它就可以工作,并且我可以在我的 Swift 类或我的 Objective-C 类上调用该方法。
我是否错过了我的 Swift 类方法定义中的一些微妙之处?这似乎不太可能,因为我能够在我的 Swift 类或我的 Objective-C 类上调用 sharedInstance() 类方法。
您可以从 Github 下载该项目,如果您愿意,可以查看一下。它被称为SwiftPerformanceBenchmark。 (链接)
【问题讨论】:
嗯。在黑暗中拍摄,但您的class func
是否应该返回符合 CalcPrimesProtocol
的 AnyObject
,而不是协议对象本身?
感谢您的建议。如果我尝试这样做:class func sharedInstance() -> AnyObject <CalcPrimesProtocol>
我得到一个编译器错误“无法专门化非泛型类型'AnyObject'”。
当。也许我会克隆项目并搞砸它。
我很想再拥有一双眼睛。我被难住了。
@nhgrif 它只是在 Objective-C 中(我已经克隆了项目并检查了)。
【参考方案1】:
在 Objective-C 中,我们总是传递指针,并且指针总是可以是 nil
。许多 Objective-C 程序员利用了这样一个事实:向nil
发送消息什么也没做,而是返回0
/nil
/NO
。 Swift 处理nil
完全不同。对象要么存在(从不存在nil
),要么不知道它们是否存在(这是 Swift 可选项发挥作用的地方)。
因此,在 Xcode 6.3 之前,这意味着任何使用任何 Objective-C 代码的 Swift 代码都必须将所有对象引用视为 Swift 可选项。 Objective-C 的语言规则并没有阻止对象指针成为nil
。
这对于使用 Swift 的 Objective-C 协议、类等意味着什么,那就是一团糟。我们不得不在不完美的解决方案之间做出选择。
给定以下 Objective-C 协议:
@protocol ObjCProtocol <NSObject>
@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;
@end
我们可以接受方法定义为包含隐式展开的选项:
class MyClass: NSObject, ObjCProtocol
func methodWithArgs(args: NSObject!)
// do stuff with args
这使得生成的代码更清晰(我们永远不必在正文中展开),但是我们将始终面临“在展开可选时发现 nil”错误的风险。
或者,我们可以将方法定义为真正的可选:
class MyClass: NSObject, ObjCProtocol
func methodWithArgs(args: NSObject?)
// unwrap do stuff with args
但这给我们留下了很多乱七八糟的解包代码。
Xcode 6.3 修复了这个问题,并为 Objective-C 代码添加了“Nullability Annotations”。
新引入的两个关键字是nullable
和nonnull
。它们在您为 Objective-C 代码声明返回类型或参数类型的地方使用。
- (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter;
- (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter;
- (nullable NSObject *)methodReturningNullableOptionalValue;
- (nonnull NSObject *)methodReturningNonNullNonOptionalValue;
除了这两个注解关键字之外,Xcode 6.3 还引入了一组宏,可以轻松地将大部分 Objective-C 代码标记为nonnull
(完全没有注解的文件实际上被假定为nullable
)。为此,我们在部分顶部使用NS_ASSUME_NONNULL_BEGIN
,在我们希望标记的部分底部使用NS_ASSUME_NONNULL_END
。
因此,例如,我们可以将您的整个协议封装在这个宏对中。
NS_ASSUME_NONNULL_BEGIN
@protocol CalcPrimesProtocol <NSObject>
- (void) calcPrimesWithComputeRecord: (ComputeRecord *) aComputeRecord
withUpdateDisplayBlock: (updateDisplayBlock) theUpdateDisplayBlock
andCompletionBlock: (calcPrimesCompletionBlock) theCalcPrimesCompletionBlock;
+ (id <CalcPrimesProtocol> ) sharedInstance;
@end
NS_ASSUME_NONNULL_END
这与将所有指针参数和返回类型标记为nonnull
(with a few exceptions, as this entry in Apple's Swift blog makes note of) 具有相同的效果。
Xcode 6.3 之前的版本
符合 Objective-C 协议的 Swift 类必须将该协议中的任何 Objective-C 类型视为可选项。
为了解决这个问题,我创建了以下 Objective-C 协议:
@protocol ObjCProtocol <NSObject>
@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;
@end
然后,创建了一个继承自 NSObject
的 Swift 类,并声明自己符合 ObjCProtocol
。
然后我继续输入这些方法名称并让 Swift 为我自动完成这些方法,这就是我得到的(我输入了方法主体,其余的如果自动完成):
class ASwiftClass : NSObject, ObjCProtocol
class func classMethod() -> ObjCProtocol!
return nil
func instanceMethod() -> ObjCProtocol!
return nil
func methodWithArgs(args: NSObject!)
// do stuff
现在,如果我们愿意,我们可以使用常规选项(使用?
)代替这些自动解包的选项。编译器对两者都非常满意。关键是我们必须考虑到nil
的可能性,因为Objective-C 协议不能阻止nil
被传递。
如果这个协议是在 Swift 中实现的,我们可以选择返回类型是否是可选的,Swift 会阻止我们将 nil
返回到一个没有定义非可选返回类型的方法。
【讨论】:
杰出(投票)。感谢您解决这个问题。是时候改变我的 Swift 方法以尝试遵守 Obj-C 协议了。 我想我说得太早了。将函数结果设为“符合 CalcPrimesProtocol”类型的可选项并不能解决问题。 编译器希望我将其声明为class func sharedInstance() -> NSObject!
。然后结果不符合我的协议。
哦。我知道了。不能将协议的方法返回类型声明为(id<CalcPrimesProtocol>)sharedInstance;
吗?您需要NSObject *
和id
有什么特别的原因吗?以上是关于如何创建符合 Swift 和 Objective-C 之间共享协议的类方法?的主要内容,如果未能解决你的问题,请参考以下文章
无法将符合协议的 Objective-C 类分配给具有 Swift 协议的属性
如何从 Swift 扩展访问 Objective-C 类的私有成员?
如何将 RxSwift 用于 Objective-C 和 Swift 项目?