Swift协议可选遵循通过非可选方式

4

我有一个带有可选属性的协议。

大多数符合此协议的类型将具有相应的可选属性。但是,其中一个具有相同类型和名称的非可选属性。

protocol SomeProtocol {
    var foo: Int? { get }
}

struct StructA: SomeProtocol {
    let foo: Int?
}

struct StructB: SomeProtocol {
    let foo: Int // Type 'StructB' does not conform to protocol 'SomeProtocol'
}

按下Xcode的“Fix-是否要添加协议存根?”按钮,会添加属性的可选版本,但现在结构体中存在无效的重复变量名称:
struct StructB: SomeProtocol {
    let foo: Int
    var foo: Int? { return foo } // Invalid redeclaration of 'foo'
}

在仅使用 { get } 的情况下,我曾经认为这会“自动运行”,因为非可选项总是满足可选项的约束条件,类似于在带有可选项返回类型的函数中返回非可选项。但显然并非如此。
对于函数也是一样的;协议的 func bar() -> Int? 无法满足声明 func bar() -> Int 的符合类型。
是否有任何方法可以解决这个问题?我不想重新命名变量或添加中间 getter。
Swift 是否考虑过这种情况?为什么不允许非可选类型满足可选协议变量?

1
协议是一种要求。如果你说可选是一种要求,那么你需要将其实现为可选,否则它将不符合你的协议。 - Leo Dabus
如果你正在实现的类/结构体保证该属性永远不会为nil,那么在访问该属性时可以直接强制解包其值。 - Leo Dabus
2
我宁愿不重命名变量或添加中间的getter。但是,您已经找到了解决方案。您的另一个选择是更改协议。理由是它不受支持。具体来说,可选项并没有被特殊处理,而这是必须的才能使其工作。 (这不适用于具有“set”的协议,也不适用于任何其他泛型,例如Array。因此,对于具有“get”的可选项来说,这将是非常神奇的。可能性很大,但需要编译器魔法,并且还没有足够常见的、设计良好的协议添加该魔法。) - Rob Napier
@RobNapier 添加了一个解决方法,对我的情况来说已经足够了。还提供了一个长期存在的 Swift 问题单链接,可以实现这一点。 - pkamb
2个回答

3
如果协议提供了一个返回可选值的默认实现:
protocol SomeProtocol {
    var foo: Int? { get }
}

extension SomeProtocol {
    var foo: Int? { return nil }
}

协议兼容的类型可以提供一个覆盖非可选版本的变量/函数:
struct StructB: SomeProtocol {
    let foo: Int
}

我在Swift Evolution论坛上发现了这个讨论:

乍一看,我以为有一个规则允许我们使用非可选类型满足协议要求,但结果出现了错误。只有进一步调查后,我才注意到必须存在默认实现才能“有点覆盖”要求与非可选版本。

https://forums.swift.org/t/how-does-this-rule-work-in-regard-of-ambiguity/19448

这个Swift团队还讨论了允许非可选类型满足可选值协议的问题:

是否允许使用非可选类型来满足协议要求,就像使用可失败的init一样?(可能会有一些隐式的可选促进。)

是的,完全可以!除了这改变了现有代码的行为,所以我们必须非常小心。这被认为是[SR-522] Protocol funcs cannot have covariant returns的一部分

这在Stack Overflow上有记录:
为什么一个协议中的只读属性要求不能由符合该属性的属性满足?

1
不错的解决方案。但要注意,当再次使用协议进行工作时,例如 func test(parameter: SomeProtocol) { print(parameter.foo) },你将始终得到nil,因为它只知道协议的实现。 - undefined

2

pkamb提供的带默认实现的扩展解决方案在编译器识别符合性方面很有帮助,但您需要注意这可能会产生奇怪的行为。

新符合类型的对象将根据您所转换的类型返回不同的值(这也包括传递到参数类型为协议的情况):

let bar = StructB(foo: 7)
let baz: SomeProtocol = bar
bar.foo                     // evaluates to 7
baz.foo                     // evaluates to nil (surprise!)

最近有人在相关的Swift错误票证上评论道:“这可能会让人感到非常惊讶,也许可以认为是它自己的一个bug?”

这确实让我困扰。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接