【问题标题】:Is there a way in Swift to partially match a generic?Swift 中有没有办法部分匹配泛型?
【发布时间】:2020-02-25 00:40:52
【问题描述】:

也就是说,如果我有一个包含两个泛型 A 和 B 的类 C,有没有办法可以将一个对象强制转换为 C 而我不在乎 B 是什么?

我的具体用例是我需要在一个多窗口但不基于文档的应用程序中连接 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 不会返回我创建的实际ContentView,它会根据使用的修饰符返回该视图的修改版本。 (在我的例子中,我使用了.environmentObject 修饰符。)因此,上面的if 语句永远不会返回true,因为类型不是NSHostingView&lt;ContentView&gt;,而是NSHostingView&lt;ModifiedContent&lt;ContentView, _bunch_Of_Gobbletygook_Representing_The_Modifiers&gt;&gt;。 “解决”问题的一种方法是在创建窗口时打印出type(of: hostingView) 的结果,然后更改我的演员表以包含当前版本的“gobbledygook”,但由于以下两个原因,这很脆弱:

  1. 如果我更改修饰符,编译器不会警告我需要更新强制转换,并且
  2. 由于“gobbledygook”包含单个下划线值,我必须假设这些是可能更改的内部细节。因此,如果我不更改任何代码,操作系统更新可能会导致演员开始失败。

所以我创建了以下 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”添加什么。我会想到某种形式的AnyAnyObject,但编译器对此抱怨。本质上,我想告诉编译器,只要 ModifiedContent 将 RootView 作为其内容类型,我不在乎 ANY 是什么。

任何想法都将不胜感激。

【问题讨论】:

  • 只是好奇为什么需要从 NSView 转到相应的 SwiftUI 视图?拿到后打算怎么处理?
  • 其实originalRootView是没有意义的,因为在SwiftUI中视图是结构体,所以即使它可以工作它也会返回根视图的副本,因为它是一个值,所以修改它不会影响NSHostingView中保存的视图。
  • 在我的例子中,我使用它来根据与特定窗口相关的数据状态启用/禁用菜单中的项目。 (例如,如果窗口中显示的日志中没有任何内容,则禁用“清除日志”菜单项。)所以你对副本是正确的,但我用它来读取状态,而不是更改它。也许我应该返回状态而不是返回视图?无论如何,我对更多基于 SwiftUI 的替代方案感兴趣,但在这个新 API 中,macOS 似乎仍然是一个“二等公民”。
  • 但是@Asperi 提出了一个很好的观点。仅仅因为我的解决方案有效并不意味着它是正确的方法。我将再看看其他方法来做到这一点,也许通过菜单中的绑定组合和组合来让视图做出反应。
  • 也就是说,我认为我最初的问题仍然是一个公平的问题,但看起来答案可能是否定的,至少在 Swift 5.1 中不是。

标签: swift generics swiftui nsview


【解决方案1】:

为了让结果正式化,答案是“不”,从 Swift 5.1 开始,无法部分匹配泛型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-21
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多