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
accept Any
。
如果您提供
private func updateInternal<T>(_ state: T) {
print("=generic")
}
当然,这可以称为,因为它实际上接受任何类型(或者,换一种说法,接受Any
)。
如果您提供
private func updateInternal(_ state: Any) {
print("=any")
}
相反,您的代码也会编译。
但是在运行代码时,只有这个通用函数在所有调用场景中都匹配
对,是真的。但是 Swift 的语义不允许编译器证明或使用这个事实。(与 C++ 不同,每个模板实例化都会导致编译器使用提供的类型单独编译模板。)
因此,我的问题是如何通过从通用上下文中调用非通用函数作为专用函数来解决这个问题
Swift 没有专门的函数,因为编译器再次编译泛型函数一次。编译器无法选择正确的专业化。同样的事情在 Swift 中通过面向对象/动态调度(virtual
在 C++ 中)实现。
您可以对可以作为泛型参数传递的类型提供约束。例如,这将起作用:
private func update<T: String>(_ state: LoadingState<T>) {
switch state {
case .loaded(let viewModel):
updateInternal(viewModel)
default:
break
}
}
因为现在编译器知道T
它将与 String 协变,它可以调用updateInternal
. 但这当然是没有意义的。
您通常会定义一个包含您需要的 T 的所有功能的协议(例如提供和 updateInternal 方法!)并将 T 约束到它。
我建议您了解有关类型约束和协议的更多信息。
推荐阅读
- mongodb - 为什么 mongo findById 不起作用但匹配 _id 是?
- typescript - 在页面之间导航时如何使菜单正确显示
- reactjs - React Hooks:即使状态没有改变也要重新渲染
- javascript - 如何为变量简化代码?
- ios - Xcode 11 导出 IPA 时出错找不到“my.bundle.id”的配置文件
- css - HTML 电子邮件 - 在文本周围放置“徽章”格式
- .net - 使用基于角色的身份验证时不调用自定义 AuthorizationFilter
- c# - 我需要什么访问级别才能访问 v2 api 中的用户完整个人资料和职位?
- python - 在 Pandas 列中有 NaN 的地方,我想用 1 替换,但需要对 1 求和的 Totals 除外
- c# - 无法将 c# .Net Core 3.0 与 directx 9.0 依赖项链接