Swift 中有没有办法部分匹配泛型?
Is there a way in Swift to partially match a generic?
也就是说,如果我有一个采用两个泛型 A 和 B 的 class C,有没有一种方法可以将对象转换为 C 而我不关心 B 是什么?
我的具体用例是我需要在多 window 但非基于文档的应用程序中连接 NSView 功能和新的 SwiftUI。我遇到的问题是,给定一个 NSView,我需要获取它正在管理的 SwiftUI 视图(在我的例子中是一个名为 ContentView
的视图)。
请注意,我确实有一个解决方案,包括在下面,但它涉及使用基于 Mirror
的反射,我想知道是否有更好的方法,很可能涉及使用 as?
转换为泛型的部分匹配。
桥接是使用 NSHostingView 完成的,因此看起来应该只执行以下操作:
if let hostingView = NSApplication.shared.keyWindow?.contentView as? NSHostingView<ContentView> {
// do what I need with 'hostingView.rootView'
}
不幸的是,NSHostingView.rootView 不是 return 我创建的实际 ContentView
,它 return 是该视图的修改版本,具体取决于所使用的修饰符。 (在我的例子中,我使用的是 .environmentObject
修饰符。)因此,上面的 if
语句永远不会 return 为真,因为类型不是 NSHostingView<ContentView>
而是 NSHostingView<ModifiedContent<ContentView, _bunch_Of_Gobbletygook_Representing_The_Modifiers>>
. "solve" 问题的一种方法是在我创建 window 时打印出 type(of: hostingView)
的结果,然后更改我的转换以包含 "gobbledygook" 的当前版本,但由于以下两个原因,它很脆弱:
- 如果我更改修饰符,编译器不会警告我需要更新转换,并且
- 由于 "gobbledygook" 包含单个下划线值,我必须假设这些是可能更改的内部细节。因此,如果我不更改任何代码,OS 更新可能会导致转换开始失败。
所以我创建了以下 NSView 扩展形式的解决方案:
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
let mirror = Mirror(reflecting: self)
if let rootView = mirror.descendant("_rootView") {
let mirror2 = Mirror(reflecting: rootView)
if let content = mirror2.descendant("content") as? RootView {
return content
}
}
return nil
}
}
这让我可以使用以下方式处理我的需求:
private func currentContentView() -> ContentView? {
return NSApplication.shared.keyWindow?.contentView?.originalRootView()
}
... sometime later ...
if let contentView = currentContentView() {
// do what I need with contentView
}
我想知道是否有一种方法可以在不使用反射的情况下实现 originalRootView
,大概是通过允许对 ModifiedContent
对象进行部分指定的强制转换。例如,类似于以下内容(无法编译):
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
if let hostingView = self as? NSHostingView<ModifiedContent<RootView, ANY>> {
return hostingView.rootView.content
}
return nil
}
}
问题是要为 "ANY" 添加什么。我会想到某种形式的 Any
或 AnyObject
,但编译器对此有所抱怨。本质上,我想告诉编译器我不关心 ANY 是什么,只要 ModifiedContent 的内容类型是 RootView。
如有任何想法,我们将不胜感激。
只是为了让结果正式,答案是“不”,从 Swift 5.1 开始,无法部分匹配通用。
也就是说,如果我有一个采用两个泛型 A 和 B 的 class C,有没有一种方法可以将对象转换为 C 而我不关心 B 是什么?
我的具体用例是我需要在多 window 但非基于文档的应用程序中连接 NSView 功能和新的 SwiftUI。我遇到的问题是,给定一个 NSView,我需要获取它正在管理的 SwiftUI 视图(在我的例子中是一个名为 ContentView
的视图)。
请注意,我确实有一个解决方案,包括在下面,但它涉及使用基于 Mirror
的反射,我想知道是否有更好的方法,很可能涉及使用 as?
转换为泛型的部分匹配。
桥接是使用 NSHostingView 完成的,因此看起来应该只执行以下操作:
if let hostingView = NSApplication.shared.keyWindow?.contentView as? NSHostingView<ContentView> {
// do what I need with 'hostingView.rootView'
}
不幸的是,NSHostingView.rootView 不是 return 我创建的实际 ContentView
,它 return 是该视图的修改版本,具体取决于所使用的修饰符。 (在我的例子中,我使用的是 .environmentObject
修饰符。)因此,上面的 if
语句永远不会 return 为真,因为类型不是 NSHostingView<ContentView>
而是 NSHostingView<ModifiedContent<ContentView, _bunch_Of_Gobbletygook_Representing_The_Modifiers>>
. "solve" 问题的一种方法是在我创建 window 时打印出 type(of: hostingView)
的结果,然后更改我的转换以包含 "gobbledygook" 的当前版本,但由于以下两个原因,它很脆弱:
- 如果我更改修饰符,编译器不会警告我需要更新转换,并且
- 由于 "gobbledygook" 包含单个下划线值,我必须假设这些是可能更改的内部细节。因此,如果我不更改任何代码,OS 更新可能会导致转换开始失败。
所以我创建了以下 NSView 扩展形式的解决方案:
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
let mirror = Mirror(reflecting: self)
if let rootView = mirror.descendant("_rootView") {
let mirror2 = Mirror(reflecting: rootView)
if let content = mirror2.descendant("content") as? RootView {
return content
}
}
return nil
}
}
这让我可以使用以下方式处理我的需求:
private func currentContentView() -> ContentView? {
return NSApplication.shared.keyWindow?.contentView?.originalRootView()
}
... sometime later ...
if let contentView = currentContentView() {
// do what I need with contentView
}
我想知道是否有一种方法可以在不使用反射的情况下实现 originalRootView
,大概是通过允许对 ModifiedContent
对象进行部分指定的强制转换。例如,类似于以下内容(无法编译):
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
if let hostingView = self as? NSHostingView<ModifiedContent<RootView, ANY>> {
return hostingView.rootView.content
}
return nil
}
}
问题是要为 "ANY" 添加什么。我会想到某种形式的 Any
或 AnyObject
,但编译器对此有所抱怨。本质上,我想告诉编译器我不关心 ANY 是什么,只要 ModifiedContent 的内容类型是 RootView。
如有任何想法,我们将不胜感激。
只是为了让结果正式,答案是“不”,从 Swift 5.1 开始,无法部分匹配通用。