Objc-C to Swift:如何在 Swift 中创建一个属性,以保证调用者使用它时它是某种类型的?



TL;DR:这是关于将 Objective-C 模式迁移到 Swift 的。最好先看看下面的Objective-C界面,以更好地了解我想要实现的目标。

我刚刚开始将一个相当大的代码库从 Objective-C 调整到 Swift。遗留代码库中有一些设计模式,以尝试提供一些类型安全性。

这些模式在 Swift 中似乎真的很不合适,但我不确定这样做的正确"Swift 方式"是什么。使用泛型感觉像是解决问题的方法,但我不清楚如何最好地进行。

目标是创建一个结构,该结构具有可以容纳"几乎任何内容"的属性。调用方期望属性在使用时属于特定类型,如果存在类型不匹配,则应引发错误或异常。(即:调用方期望参数是整数,但实际上存储了一个字符串。

struct Command<T> {
let directive: Directive
let argument: T
}
let command = Command(directive: .draw, argument: NSZeroRect)
let command2 = Command(directive: .toggle, argument: true)
// Somewhere else in the code...
//
// How do I pass in a Command<> here? 
// This generates an error because Command<Bool> cannot be converted to Command<Any>
//
func processCommand(_ command:Command<Any>) {
switch command.directive {
case .draw:
// How do I ensure that command.argument is indeed an NSRect?
case .toggle:
// How do I ensure that command.argument is indeed a boolean?
}
}

Objective-C界面看起来像这样。请注意,参数可以是许多不同的类型。范围从基元(整数,布尔值,双精度等)到可以存储在NSValue中或支持NSCoding的任何内容。

每种类型有多个属性访问器,只要有意义。

@interface FLCommand : NSObject
@property(assign, readonly) FLDirective directive;
@property(strong, readonly) id argument;
@property(strong, readonly) BOOL argumentAsBoolean;
@property(strong, readonly) NSRect argumentAsRect;
- (instancetype)initWithDirective:(FLDirective)directive booleanArgument:(BOOL)value;
- (instancetype)initWithDirective:(FLDirective)directive rectArgument:(NSRect)rect;
- (instancetype)initWithDirective:(FLDirective)directive argument:(id)arg;
@end
@implementation FLCommand
- (instancetype)initWithDirective:(FLDirective)directive
booleanValue:(BOOL)value {
// Convert boolean to object.
return [self initWithDirective:directive 
argument:@(value)];
}
- (instancetype)initWithDirective:(FLDirective)directive
rectArgument:(NSRect)rect {
// Convert NSRect to object.
return [self initWithDirective:directive 
argument:[NSValue valueWithRect:rect]];
}
- (BOOL)argumentAsBoolean {
NSAssert([_argument isKindOfClass:NSNumber.class], @"Expected argument to be an NSNumber.");
return [self.argument boolValue];
}
- (NSRect)argumentAsRect {
NSAssert([_argument isKindOfClass:NSValue.class], @"Expected command argument to be an NSValue.");
return [(NSValue *)self.argument rectValue];
}
@end
// Somewhere else in the code the commands are acted upon. Using the 
// asserts and type-specific property accessors offers a poor-man's 
// way of doing type safety to ensure the the command's argument is 
// of the expected type.
- (void)processCommand:(FLCommand *)command {
switch (command.directive) {
case FLDirectiveToggleSomething:
// The assert will fire if the argument is not a boolean.
[self toggleSomething:command.argumentAsBoolean];
break;
case FLDirectiveDrawSomething:
[self drawSomethingInFrame:command.argumentAsRect];
break;
}
}
}

在我看来,在 Swift中使用等效模式似乎非常不像 Swift。有没有更好的方法来使用泛型来解决这个问题?

Swift 5 和 macOS 10.15+ 解决方案都可以。

您是否考虑过使用具有关联值的枚举(通常称为复杂枚举)

enum Directive {
case draw(NSRect)
case toggle(Bool)
}
struct Command {
let directive: Directive
}
let command = Command(directive: .draw(.zero))
let command2 = Command(directive: .toggle(true))
func processCommand(_ command: Command) {
switch command.directive {
case .draw(let rect):
// do something with rect
case .toggle(let value):
// do something with the value
}
}

(您实际上可以完全跳过上面的Command结构)

或者另一种解决方案是使用具有关联类型的协议:

protocol Command {
associatedtype AssociatedType
var argument: AssociatedType { get }
init(_ argument: AssociatedType)
func process()
}
struct DrawCommand: Command {
typealias AssociatedType = NSRect
let argument: AssociatedType
init(_ argument: AssociatedType) {
self.argument = argument
}
func process() {
print("draw something with (argument)")
}
}
struct ToggleCommand: Command {
typealias AssociatedType = Bool
let argument: AssociatedType
init(_ argument: AssociatedType) {
self.argument = argument
}
func process() {
print("toggle something with (argument)")
}
}
let command = DrawCommand(.zero)
let command2 = ToggleCommand(true)
command.process()
command2.process()

这有更多的样板/重载,但提供了更好的关注点分离,并且将来引入更多命令时会更灵活,而无需更新代码中的多个位置。

最新更新