我们试图确定这是Swift中的错误,还是我们滥用泛型,选项,类型推断和/或无合并运算符。
我们的框架包含一些用于将字典解析为模型的代码,并且我们使用默认值来解决可选属性的问题。
我们有一个协议SomeProtocol
和两个在协议扩展中定义的通用函数:
mapped<T>(...) -> T?
mapped<T : SomeProtocol>(...) -> T?
我们的结构和类遵循此协议,然后在协议所需的init函数内解析它们的属性。
在init(...)
函数中,我们尝试设置属性someNumber
的值,如下所示:
someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber
字典当然包含密钥someNumber
的实际值。但是,这将始终失败,并且永远不会从mapped()
函数返回实际值。
要么注释掉第二个泛型函数,要么强制向下转换赋值的rhs上的值将解决这个问题,但我们认为这应该按照当前的方式工作。
以下是演示此问题的完整代码段,以及(暂时)修复代码中标记为OPTION 1
和OPTION 2
的问题的两个选项:
import Foundation
// Some protocol
protocol SomeProtocol {
init(dictionary: NSDictionary?)
}
extension SomeProtocol {
func mapped<T>(dictionary: NSDictionary?, key: String) -> T? {
guard let dictionary = dictionary else {
return nil
}
let source = dictionary[key]
switch source {
case is T:
return source as? T
default:
break
}
return nil
}
// ---
// OPTION 1: Commenting out this makes it work
// ---
func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? {
return nil
}
}
// Some struct
struct SomeStruct {
var someNumber: Double? = 0.0
}
extension SomeStruct: SomeProtocol {
init(dictionary: NSDictionary?) {
someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber
// OPTION 2: Writing this makes it work
// someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber!
}
}
// Test code
let test = SomeStruct(dictionary: NSDictionary(object: 1234.4567, forKey: "someNumber"))
if test.someNumber == 1234.4567 {
print("success \(test.someNumber!)")
} else {
print("failure \(test.someNumber)")
}
请注意,这是一个错过mapped
函数的实际实现的示例,但结果是相同的,并且为了这个问题,代码应该足够了。
编辑:我曾经报告过这个问题,现在它被标记为已修复,所以希望这在Swift 3中不再发生。
https://bugs.swift.org/browse/SR-574
答案 0 :(得分:7)
你给编译器提供了太多选项,它选错了(至少不是你想要的那个)。问题是,每T
个问题都可以轻微提升到T?
,包括T?
(提升为T??
)。
someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber
哇。这种类型。所以可选。 :d
那么Swift如何开始解决这个问题呢。好吧,someNumber
是Double?
,因此它会尝试将其转换为:
Double? = Double?? ?? Double?
这有用吗?让我们从最具体的方面开始寻找一个通用的mapped
。
func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? {
要完成这项工作,T
必须为Double?
。是Double?:SomeProtocol
吗?不。继续前进。
func mapped<T>(dictionary: NSDictionary?, key: String) -> T? {
这有用吗?当然! T
可以是Double?
我们会返回Double??
,一切都会结算。
那为什么这个有用?
someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber!
这解析为:
Double? = Optional(Double? ?? Double)
然后事情按照你认为的方式运作。
小心这么多Optionals。 someNumber
真的必须是可选的吗?应该是throw
中的任何一个吗? (我不是建议throw
是针对可选问题的一般解决方法,但至少这个问题让你有时间考虑这是否真的是一个错误条件。)
以mapped
的方式在Swift中以返回值的形式独占类型参数化几乎总是一个坏主意。这往往是Swift中的一个真正的混乱(或者任何具有大量类型推断的通用语言,但是当涉及Optionals时它在Swift中真的会爆炸)。类型参数通常应出现在参数中。如果你尝试这样的话,你会看到问题:
let x = test.mapped(...)
无法推断x
的类型。这不是一种反模式,有时麻烦是值得的(公平地说,你正在解决的问题可能就是这种情况之一),但如果可以,就要避免它。
但正是选择权正在杀死你。
编辑:Dominik提出了一个非常好的问题,即当mapped
的约束版本被删除时,为什么这种行为会有所不同。我不知道。显然,类型匹配引擎会根据mapped
通用的方式以不同的顺序检查有效类型。您可以通过将print(T.self)
添加到mapped<T>
来查看此内容。这可能被认为是编译器中的一个错误。