【问题标题】:swift method parameter of type that implements a protocol?实现协议的类型的快速方法参数?
【发布时间】:2016-11-30 17:12:02
【问题描述】:

尝试在 swift 中实现类似于以下目标 c 代码的内容 UIViewController<Routable> 但它抱怨它无法推断出泛型类型

我认为它希望我以某种方式定义要使用的 UIViewController 的子类,但我不在乎。我只想要一个实现可路由的 UIViewController

public protocol Routable {
    static func provideInstance(id: String?) -> Self?
}

public class Router {
    public func getDestinationViewController<T: UIViewController>(url: URL) -> T? where T: Routable {
        return nil
    }
}

// Generic parameter `T` could not be inferred
let vc = Router().getDestinationViewController(url: URL(string: "www.domain.com/users/1")!)

我知道我可以使用强制转换来解决问题,只需让方法返回 UIViewController 或可路由,但我宁愿以正确的方式进行

【问题讨论】:

    标签: swift generics protocols


    【解决方案1】:

    在这种情况下,您希望vc 是什么精确类型? (我的意思是在编译时可以确定类型,查看上面的代码,而不是“某些类型我们直到运行时才知道。”)如果你不能回答这个问题,编译器也不能。

    这里的正确方法是“返回 UIViewController 或可路由”,正如您所说的那样。这就是你目前所知道的一切。试图说更精确的东西是不正确的。

    vc 是类型推断的这一事实只是为了方便。它仍然必须在编译时具有特定的已知类型。源码里写不出来,编译器就无法推断出来。

    【讨论】:

    • 真的不能返回一个同样是可路由的 UIViewController 吗?这在 objc - (UIViewController *&lt;Routable&gt;)gimeeSomething 中是可能的
    • 在 Swift 中无法表达这一点。您必须将您想要的任何特定方法从 UIViewController 添加到 Routable 协议中。这是一个众所周知的限制,可能会在未来的 Swift 版本中得到改进,但目前是不可能的。
    【解决方案2】:

    当您使用泛型函数时,泛型参数的类型是从您将结果分配给的变量的类型推断出来的(或者,对于返回 Void 的函数,从您指定的参数的具体类型推断出来)传递给T 类型的参数)。

    在您的示例中,您没有告诉编译器您的 vc 变量是什么类型,因此您会收到错误消息。您可以像这样轻松解决此问题:

    class MyViewController: UIViewController, Routable {
        public static func provideInstance(id: String?) -> Self? {
            return self.init()
        }
    }
    
    // give `vc` a concrete type so `T` can be inferred:
    let vc: MyViewController? = Router().getDestinationViewController(url: URL(string: "www.domain.com/users/1")!)
    

    编辑:

    由于您的问题似乎更接近于“如何复制 Objective-C 的 UIViewController *&lt;Routable&gt; 概念”,而您在 Swift 中无法做到这一点,我会提到您可能会发现值得回顾你的设计。当我从 Objective-C 迁移到 Swift 时,我认为无法使用像 UIViewController *&lt;Routable&gt; 这样的东西很难解决(甚至向 Apple 提交了一个错误报告),但实际上这不是问题。

    您的Router 类需要您的视图控制器中的一些 信息才能正确路由。在您的示例中,您正在寻找与特定 URL 关联的视图控制器。这意味着您的 UIViewController 有一个包含此 URL 的 property,因此与其返回 Routable 协议,正确的方法是像这样子类化 UIViewController:

    class RoutableViewController: UIViewController {
        let url: URL = URL(string: "...")
    }
    

    现在你的路由器看起来像:

    class Router {
        var routables = [RoutableViewController]()
    
        func viewControllerWithURL(_ url: URL) -> RoutableViewController? {
            return routables.first
        }
    }
    

    现在您不需要进行任何类型转换,并且(显然)维护了类型安全。

    编辑 2:

    这是一个使 每个 视图控制器符合 Routable 的解决方案(与我在之前的编辑中提出的解决方案相比,我认为您不会获得太多收益,但无论如何都是这样):

    protocol Routable {
        var url: URL? { get }
    }
    
    // now every UIViewController conforms to Routable
    extension UIViewController: Routable {
        var url: URL? { return nil }
    }
    
    class MyViewController: UIViewController {
        private let myURL: URL
    
        override var url: URL? { return myURL }
    
        init(url: URL) {
            myURL = url
            super.init(nibName: nil, bundle: nil)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
    class Router {
        var viewControllers: [UIViewController] = [
            MyViewController(url: URL(string: "foo")!),
            UIViewController()
        ]
    
        func viewController(for url: URL) -> UIViewController? {
            for viewController in viewControllers {
                if viewController.url == url { return viewController }
            }
    
            return nil
        }
    }
    

    【讨论】:

    • 这行不通,因为我不知道我要返回什么 vc,我希望它是动态的。路由器遍历我注册的路由并找到匹配项并返回它而不知道它是哪个vc
    • 那么你想要一个返回UIViewController?的函数,你必须在switch语句中使用as?或多个cases进行类型转换(例如case is MyViewController:)。泛型用于为未知参数提供方法实现,不允许任意向下转换。
    • “我知道我可以使用强制转换来解决问题,只需让方法返回 UIViewController 或可路由,但我宁愿以正确的方式做”这在目标 c 中是可能的,我'我很惊讶 Swift 不支持这个
    • 我确实阅读了您的问题 :) ... Swift 旨在成为一种比 Objective-C 更安全的语言,因此目前“正确的方法”是一种使用类型转换的解决方案。参见例如stackoverflow.com/a/26403660/312594
    • 我已经用另一种类型安全的方法更新了我的答案。请看一看。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-23
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 2019-07-05
    • 2014-08-05
    相关资源
    最近更新 更多