我最近一直在学习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版本完全实现上述定义的方法,无需担心。
快速版本有一个方法:
class func sharedInstance() -> CalcPrimesProtocol
但是,如果将该方法作为协议的必需方法,则会出现编译器错误“类型” CalcPrimesSwift不符合协议“ CalcPrimesProtocol”。
但是,如果我在协议中将单例类方法sharedInstance标记为可选,那么它将起作用,并且可以在我的Swift类或Objective-C类上调用该方法。
我在Swift类方法的定义中是否错过了一些微妙之处?考虑到我能够在Swift类或Objective- C类上调用sharedInstance()类方法,这似乎不太可能。
您可以从Github下载该项目,然后查看是否需要。它称为 SwiftPerformanceBenchmark 。(链接)
在Objective-C中,我们总是在传递指针,而指针始终可以是nil。Objective- C的程序员很多利用的事实,将消息发送给nil什么也没做,返回0/ nil/ NO。Swift的处理方式nil完全不同。对象要么存在(从不存在nil),要么不知道它们是否存在(这是Swift可选对象起作用的地方)。
nil
0
NO
因此,在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代码添加了“可空性注释”。
新引入的两个关键字是nullable和nonnull。它们在为Objective-C代码声明返回类型或参数类型的地方使用。
nullable
nonnull
- (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
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(有一些例外,如Apple的Swift博客中的此项记为)。
符合Objective-C协议的Swift类必须将该协议中的所有Objective-C类型都视为可选。
为了弄清楚这一点,我创建了以下Objective-C协议:
然后,创建一个Swift类,该类继承自NSObject并且声明自己与this兼容ObjCProtocol。
NSObject
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未定义非可选返回类型的方法。