如何创建符合 Swift 和 Objective-C 之间共享协议的 class 方法?
How to create class methods that conform to a protocol shared between Swift and Objective-C?
我最近一直在学习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 实现了上面定义的方法,不用担心。
swift版有方法:
class func sharedInstance() -> CalcPrimesProtocol
但是,如果我将该方法作为协议的必需方法,我会收到编译器错误 "Type "CalcPrimesSwift 不符合协议 'CalcPrimesProtocol'。
但是,如果我在协议中将单例 class 方法 sharedInstance 标记为可选,它就可以工作,并且我可以在我的 Swift class 或 Objective-C class.
我是否遗漏了 Swift class 方法定义中的一些细微之处?这似乎不太可能,因为我能够在我的 Swift class 或我的 Objective-C class.[= 上调用 sharedInstance() class 方法17=]
您可以从 Github 下载该项目,并根据需要查看。它被称为SwiftPerformanceBenchmark。 (link)
在Objective-C中,我们一直在传递指针,而指针总是可以是nil
。许多 Objective-C 程序员利用了这样一个事实,即向 nil
发送消息什么都不做,然后 return 编辑了 0
/nil
/NO
。 Swift 以完全不同的方式处理 nil
。对象要么存在(从不 nil
),要么不知道它们是否存在(这就是 Swift 选项发挥作用的地方)。
在 Xcode 6.3 之前,这意味着任何使用任何 Objective-C 代码的 Swift 代码都必须将所有对象引用视为 Swift 可选。 Objective-C 的语言规则没有阻止对象指针成为 nil
.
这对于使用来自 Swift 的 Objective-C 协议、classes 等意味着什么,这是一个巨大的混乱。我们不得不在不完美的解决方案之间做出选择。
给定以下 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 代码声明 return 类型或参数类型的相同位置。
- (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter;
- (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter;
- (nullable NSObject *)methodReturningNullableOptionalValue;
- (nonnull NSObject *)methodReturningNonNullNonOptionalValue;
除了这两个注解关键字外,Xcode6.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
这与将所有指针参数和 return 类型标记为 nonnull
(with a few exceptions, as this entry in Apple's Swift blog makes note of) 具有相同的效果。
前Xcode6.3
符合 Objective-C 协议的 Swift class 必须将该协议中的任何 Objective-C 类型视为可选类型。
为了解决这个问题,我创建了以下 Objective-C 协议:
@protocol ObjCProtocol <NSObject>
@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;
@end
然后,创建了一个 Swift class 继承自 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 中实现,我们将可以选择 return 类型是否可选,并且 Swift 会阻止我们 return ing nil
到未定义非可选 return 类型的方法。
我最近一直在学习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 实现了上面定义的方法,不用担心。
swift版有方法:
class func sharedInstance() -> CalcPrimesProtocol
但是,如果我将该方法作为协议的必需方法,我会收到编译器错误 "Type "CalcPrimesSwift 不符合协议 'CalcPrimesProtocol'。
但是,如果我在协议中将单例 class 方法 sharedInstance 标记为可选,它就可以工作,并且我可以在我的 Swift class 或 Objective-C class.
我是否遗漏了 Swift class 方法定义中的一些细微之处?这似乎不太可能,因为我能够在我的 Swift class 或我的 Objective-C class.[= 上调用 sharedInstance() class 方法17=]
您可以从 Github 下载该项目,并根据需要查看。它被称为SwiftPerformanceBenchmark。 (link)
在Objective-C中,我们一直在传递指针,而指针总是可以是nil
。许多 Objective-C 程序员利用了这样一个事实,即向 nil
发送消息什么都不做,然后 return 编辑了 0
/nil
/NO
。 Swift 以完全不同的方式处理 nil
。对象要么存在(从不 nil
),要么不知道它们是否存在(这就是 Swift 选项发挥作用的地方)。
在 Xcode 6.3 之前,这意味着任何使用任何 Objective-C 代码的 Swift 代码都必须将所有对象引用视为 Swift 可选。 Objective-C 的语言规则没有阻止对象指针成为 nil
.
这对于使用来自 Swift 的 Objective-C 协议、classes 等意味着什么,这是一个巨大的混乱。我们不得不在不完美的解决方案之间做出选择。
给定以下 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 代码声明 return 类型或参数类型的相同位置。
- (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter;
- (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter;
- (nullable NSObject *)methodReturningNullableOptionalValue;
- (nonnull NSObject *)methodReturningNonNullNonOptionalValue;
除了这两个注解关键字外,Xcode6.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
这与将所有指针参数和 return 类型标记为 nonnull
(with a few exceptions, as this entry in Apple's Swift blog makes note of) 具有相同的效果。
前Xcode6.3
符合 Objective-C 协议的 Swift class 必须将该协议中的任何 Objective-C 类型视为可选类型。
为了解决这个问题,我创建了以下 Objective-C 协议:
@protocol ObjCProtocol <NSObject>
@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;
@end
然后,创建了一个 Swift class 继承自 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 中实现,我们将可以选择 return 类型是否可选,并且 Swift 会阻止我们 return ing nil
到未定义非可选 return 类型的方法。