2016-11-30 83 views
0

努力实现像在迅速 UIViewController<Routable>以下目标C代码,但抱怨说,它不能推断泛型类型实现协议的类型的swift方法参数?

我觉得要我以某种方式确定使用什么子类的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或可路由的一个UIViewController,但我宁愿做正确的方式

回答

3

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

这里的正确方法是“返回UIViewController或Routable”,就像您说的那样。这就是你现在知道的一切。试图说更精确的东西将是不正确的。

vc是类型推断的事实只是一个方便。它在编译时仍然必须具有特定的已知类型。如果你不能在源代码中编写它,编译器不能推断它。

+0

是否真的不可能返回也是可路由的UIViewController?这在objc中是可能的 - (UIViewController * )gimeeSomething' – aryaxt

+1

在Swift中无法表达这一点。你将不得不在UIViewController中添加你想要的特定方法到Routable协议上。这是一个众所周知的限制,在未来的Swift版本中可能会得到改进,但目前是不可能的。 –

1

在使用泛型函数时,通用参数的类型可以根据您分配结果的变量的类型(或者在返回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")!) 

编辑:

由于您的问题似乎是一起的“我怎么复制的UIViewController *<Routable> Objective-C的概念”线以上,这你不能在Swift中做,我会提到你可能会觉得值得回顾一下你的设计。当我从Objective-C移动到Swift时,我认为无法使用类似UIViewController *<Routable>这样的东西很难解决(甚至向Apple提交了一个关于它的错误报告),但实际上它并不是一个问题。

您的Router类需要一些信息在您的视图控制器能够正确路由。在你的例子中,你正在寻找一个与特定URL关联的视图控制器。言下之意是,你UIViewController财产包含此URL,因此而不是返回一个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 
    } 
} 
+0

这不起作用,因为我不知道什么vc我会回来,我希望它是动态的。路由器通过我的注册路由并找到一个匹配,并返回它不知道哪个vc是 – aryaxt

+0

然后你想要一个返回'UIViewController?'的函数,你必须使用'as?'或多个'case' s在'switch'语句中(例如'case是MyViewController:')。泛型被用来为尚未知的参数提供方法实现,而不允许任意的向下转换。 – par

+0

“我知道我可以使用投射来解决问题,只是让方法返回UIViewController或Routable,但我宁愿以正确的方式做到这一点在目标c中是可能的,我很惊讶Swift不支持这个 – aryaxt