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

Dun*_*n C 9 interop objective-c objective-c-protocol swift objective-c-nullability

我最近一直在学习Swift.

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

该程序计算大量素数.

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

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

+ (NSObject <CalcPrimesProtocol> *) sharedInstance;
Run Code Online (Sandbox Code Playgroud)

整个协议看起来像这样:

#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
Run Code Online (Sandbox Code Playgroud)

该类的Objective-C版本完全按照上面的定义实现方法,不用担心.

swift版本有一个方法:

  class func sharedInstance() -> CalcPrimesProtocol
Run Code Online (Sandbox Code Playgroud)

但是,如果我使该方法成为协议的必需方法,我得到编译器错误"类型"CalcPrimesSwift不符合协议'CalcPrimesProtocol'.

但是,如果我在协议中将单例类方法sharedInstance标记为可选,那么它可以工作,我可以在我的Swift类或Objective-C类上调用该方法.

我是否错过了Swift类方法定义中的一些细微之处?考虑到我可以在我的Swift类或Objective-C类上调用sharedInstance()类方法,这似乎不太可能.

您可以从Github下载该项目,如果您愿意,可以查看它.它叫做SwiftPerformanceBenchmark.(链接)

nhg*_*rif 11

在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
Run Code Online (Sandbox Code Playgroud)

我们可以接受方法定义为包含隐式解包的选项:

class MyClass: NSObject, ObjCProtocol {
    func methodWithArgs(args: NSObject!) {
        // do stuff with args
    }
}
Run Code Online (Sandbox Code Playgroud)

这使得生成的代码更清晰(我们永远不必在体内展开),但是我们将始终面临"在解包可选时找到nil"错误的风险.

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

class MyClass: NSObject, ObjCProtocol {
    func methodWithArgs(args: NSObject?) {
        // unwrap do stuff with args
    }
}
Run Code Online (Sandbox Code Playgroud)

但这给我们带来了很多乱七八糟的解包代码.

Xcode 6.3修复了这个问题,并为Objective-C代码添加了"Nullability Annotations".

这两个新引入的关键字是nullablenonnull.这些用于您声明Objective-C代码的返回类型或参数类型的相同位置.

- (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter;
- (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter;
- (nullable NSObject *)methodReturningNullableOptionalValue;
- (nonnull NSObject *)methodReturningNonNullNonOptionalValue;
Run Code Online (Sandbox Code Playgroud)

除了这两个注释关键字之外,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
Run Code Online (Sandbox Code Playgroud)

这与标记所有指针参数和返回类型具有相同的效果nonnull(除了一些例外,因为Apple的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
Run Code Online (Sandbox Code Playgroud)

然后,创建了一个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
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果需要,我们可以使用常规选项(使用?)代替这些自动解包的选项.编译器非常满意.关键在于我们必须考虑到的可能性nil,因为Objective-C协议无法阻止nil传递.

如果这个协议是在Swift中实现的,我们可以选择返回类型是否可选,Swift会阻止我们返回nil一个没有定义非可选返回类型的方法.