gpt4 book ai didi

objective-c - Objc-C 到 Swift : How to create a property in Swift that guarantees it's of a certain type when callers use it?

转载 作者:搜寻专家 更新时间:2023-11-01 06:52:03 25 4
gpt4 key购买 nike

TL;DR:这是关于将 Objective-C 模式迁移到 Swift 的。最好先查看下面的 Objective-C 接口(interface),以便更好地理解我要实现的目标。

我刚刚开始将相当大的代码库从 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 界面看起来像这样。请注意,参数可以是许多不同的类型。从基本类型(整数、 bool 值、 double 等)到任何可以存储在 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):

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

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

关于objective-c - Objc-C 到 Swift : How to create a property in Swift that guarantees it's of a certain type when callers use it?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56514593/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com