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



我最近一直在学习Swift。

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

该程序计算大量素数。

我定义了一个Swift和计算对象的Objective-C版本都应该遵守的协议

这两个对象都是singleton,所以我在Objective-C中创建了一个典型的singleton访问方法:

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

然而,如果我将该方法作为协议的必需方法,我会收到编译器错误"Type"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的处理方式完全不同。对象要么存在(从来没有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
    }
}

这使得生成的代码更干净(我们永远不必在主体内展开),然而,我们将始终面临"打开"的风险;在展开可选"时发现为零;错误

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

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

但这给我们打开代码留下了很多麻烦。

Xcode 6.3修复了这个问题;Nullability Annotations";用于Objective-C代码。

两个新引入的关键字是nullablenonnull。它们与您为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具有相同的效果(只有少数例外,正如苹果Swift博客中的这篇文章所指出的那样)。


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

然后,创建了一个Swift类,它继承了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返回到没有定义非可选返回类型的方法。

最新更新