如何创build符合在Swift和Objective-C之间共享的协议的类方法?

我最近一直在学习Swift。

我决定编写一个混合的Swift / Objective-C应用程序,它使用两种语言实现的相同algorithm进行计算密集型任务。

该程序计算大量素数。

我定义了一个协议,Swift和Objective-C版本的计算对象都应该符合。

这些对象都是单例,所以我在Objective-C中创build了一个典型的单例访问方法:

+ (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 

但是,如果我使该方法成为协议的必需方法,则会出现编译器错误“types”CalcPrimesSwift不符合协议“CalcPrimesProtocol”。

如果我在协议中将singleton类的方法sharedInstance标记为可选的,它就可以工作,我可以在Swift类或Objective-C类中调用该方法。

我在我的Swift类方法的定义中错过了一些微妙的东西吗? 这似乎不太可能,因为我可以调用我的Swift类或Objective-C类的sharedInstance()类方法。

你可以从Github上下载这个项目,如果你愿意的话可以看看。 它被称为SwiftPerformanceBenchmark 。 (链接)

在Objective-C中,我们总是传递指针,指针总是nil 。 许多Objective-C程序员都使用这样一个事实,即发送一条消息给nil什么都不做,并返回0 / nil / NO 。 Swift处理完全不同。 对象或者存在(从不nil ),或者不知道它们是否存在(这是Swift选项的作用)。

在Xcode 6.3之前,这意味着任何使用任何Objective-C代码的Swift代码都必须将所有对象引用视为Swift可选项。 Objective-C的语言规则没有任何内容阻止对象指针nil

这意味着从Swift中使用Objective-C协议,类,等等,这是一个巨大的混乱。 我们不得不select非完美的解决scheme。

鉴于以下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 } } 

这使得得到的代码更清晰(我们从来不需要在正文中进行解包),但是我们将始终处于“发现零,而解包可选”错误的风险。

或者,我们可以将该方法定义为一个真正的可选项:

 class MyClass: NSObject, ObjCProtocol { func methodWithArgs(args: NSObject?) { // unwrap do stuff with args } } 

但是这给我们留下了很多乱七八糟的代码。

Xcode 6.3修复了这个问题,并为Objective-C代码添加了“可空性注释”。

两个新引入的关键字可以是nullable ,也可以是非nullable 。 这些用在你声明Objective-C代码的返回types或参数types的地方。

 - (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter; - (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter; - (nullable NSObject *)methodReturningNullableOptionalValue; - (nonnull NSObject *)methodReturningNonNullNonOptionalValue; 

除了这两个注释关键字之外,Xcode 6.3还引入了一组macros,可以很容易地将大部分Objective-C代码标记nonnull nullable (没有注释的文件实际上被假定为nullable )。 为此,我们在该部分的顶部使用NS_ASSUME_NONNULL_BEGIN ,在我们希望标记的部分的底部使用NS_ASSUME_NONNULL_BEGIN

所以,例如,我们可以把你的整个协议包装在这个macros对里面。

 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 

这与将所有指针参数和返回types标记nonnull ( 除了less数例外,如Apple的Swift博客中的这一条logging )相同。


Pre-Xcode 6.3

符合Objective-C协议的Swift类必须将该协议中的任何Objective-Ctypes视为可选项。

为了解决这个问题,我创build了下面的Objective-C协议:

 @protocol ObjCProtocol <NSObject> @required + (id<ObjCProtocol>)classMethod; @required - (id<ObjCProtocol>)instanceMethod; @required - (void)methodWithArgs:(NSObject *)args; @end 

然后,创build一个从NSObjectinheritance的Swift类,并声明自己符合这个ObjCProtocol

然后我继续input这些方法的名称,并让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中实现的,我们可以select是否返回types是可选的,Swift会阻止我们将nil返回给没有定义非可选返回types的方法。