问题描述
我在理解泛型函数中的匹配类型如何在 swift 中工作时遇到问题。我不明白为什么我的代码无法编译。
这就是我所拥有的:
enum LoadingState<T> {
case idle
case loading
case Failed(Error)
case loaded(T)
}
private func updateInternal(_ state: Int) {
print("=int")
}
private func updateInternal(_ state: String) {
print("=string")
}
private func update<T>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewmodel):
updateInternal(viewmodel)
default:
break
}
}
let stateInt: LoadingState<Int> = .loaded(42)
let stateString: LoadingState<String> = .loaded(String("Hello"))
update(stateInt)
update(stateString)
我收到以下编译错误:
error: no exact matches in call to global function 'updateInternal'
但是,如果我为 updateInternal
添加通用函数,我就可以编译代码:
private func updateInternal<T>(_ state: T) {
print("=generic")
}
但是当运行代码时,只有这个通用函数在所有调用场景中匹配。我能够通过这个解决这个问题:
private func updateInternal<T>(_ state: T,type: T.Type) {
if type == Int.self {
updateInternal(state as! Int)
}
else if type == String.self {
updateInternal(state as! String)
}
}
private func update<T>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewmodel):
updateInternal(viewmodel,type: T.self)
default:
break
}
}
但我很确定这应该没有必要吧?因此,我的问题是如何通过将通用上下文中的非通用函数作为专用函数调用来解决此问题
解决方法
您的代码看起来很像您习惯于 C++ 模板编程(查看您的个人资料似乎证实了我的假设)。但是 Swift 泛型不是模板!
在这个函数中
private func update<T>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewModel):
updateInternal(viewModel)
default:
break
}
}
编译器将完全一次编译(或至少类型检查)这个函数,这与 C++ 模板不同。
它将检查是否可以使用泛型类型 updateInternal
的值调用 T
。
它如何检查?它将查看 T
的约束并尝试将 T
减少到在您的函数之外也可用的最具体的类型。由于您没有将 T
限制为任何类型,因此编译器只知道 T
的所有值都将协变(兼容)Any
。但是您没有提供任何接受 updateInternal
的 Any
重载。
如果您提供
private func updateInternal<T>(_ state: T) {
print("=generic")
}
这当然可以被调用,因为它实际上接受任何类型(或者换种说法,接受 Any
)。
如果您提供
private func updateInternal(_ state: Any) {
print("=any")
}
相反,您的代码也会编译。
但是当运行代码时,只有这个通用函数在所有调用场景中都匹配
是的,没错。但是 Swift 的语义不允许编译器证明或使用这个事实。 (与 C++ 不同,在 C++ 中,每个模板实例化都会导致编译器使用提供的类型单独编译模板。)
因此,我的问题是如何通过将通用上下文中的非通用函数作为专用函数调用来解决此问题
Swift 没有专门的函数,因为再一次,编译器只编译泛型函数一次。编译器无法选择正确的专业化。同样的事情在 Swift 中通过面向对象/动态分派(C++ 中的 virtual
)实现。
您可以对可以作为泛型参数传递的类型提供约束。例如,这将起作用:
private func update<T: String>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewModel):
updateInternal(viewModel)
default:
break
}
}
因为现在编译器知道 T
将与 String 协变并且它可以调用 updateInternal
。但这当然毫无意义。
您通常会定义一个协议,该协议包含您需要的 T 的所有功能(例如提供和 updateInternal 方法!)并将 T 约束到它。
我建议您详细了解类型约束和协议。