如何声明一个同时具有类型和实现某个协议的变量?

43

我的应用程序有一个详细视图控制器协议,规定它们必须具有一个viewModel属性:

protocol DetailViewController: class {
    var viewModel: ViewModel? {get set}
}

我还有一些实现该协议的不同类:

class FormViewController: UITableViewController, DetailViewController {
    // ...
}

class MapViewController: UIViewController, DetailViewController {
    // ...
}

我的主视图控制器需要一个属性,可以设置为实现了DetailViewController协议的任何UIViewController子类。

不幸的是,我找不到如何做到这一点的任何文档。在Objective-C中,这将是微不足道的:

@property (strong, nonatomic) UIViewController<DetailViewController>;

看起来在Swift中没有可用于此的语法。我所做过的最接近的尝试是在我的类定义中声明一个泛型:

class MasterViewController<T where T:UIViewController, T:DetailViewController>: UITableViewController {
    var detailViewController: T?
    // ...
}

但是我遇到了一个错误,提示“类'MasterViewController'没有实现其超类的必需成员”

这似乎在Swift中应该和Objective-C一样容易,但我找不到任何地方提供如何解决的建议。


MasterViewController 中有哪些 init 方法? - nsinvocation
我已经在https://dev59.com/3WAf5IYBdhLWcg3w9mps#35069853上发布了解决方案。 - Jans
3个回答

13

我认为你可以通过向 UIViewController 添加一个(空的)扩展,然后使用一个由空扩展和你的 DetailViewController 组成的协议来指定你的 detailViewController 属性。就像这样:

protocol UIViewControllerInject {}
extension UIViewController : UIViewControllerInject {}

现在所有继承 UIViewController 的子类都满足协议 UIViewControllerInject。然后,只需:

typealias DetailViewControllerComposed = protocol<DetailViewController, UIViewControllerInject>

class MasterViewController : UITableViewController {
  var detailViewController : DetailViewControllerComposed?
  // ...
}

但这并不是特别“自然”。

=== 编辑,添加 ===

如果您使用我的建议的 UIViewControllerInject 定义您的 DetailViewController,那么您可以使它变得更好。像这样:

protocol UIViewControllerInject {}
extension UIViewController : UIViewControllerInject {}

protocol DetailViewController : UIViewControllerInject { /* ... */ }

现在您无需显式地组合某些内容(我的DetailViewControllerComposed),可以使用DetailViewController?作为detailViewController的类型。


1
我并不是非常兴奋每个UIViewController子类都将拥有一个viewModel属性,但至少可以让事情编译 :) - Frank Schmitt
不,我并没有这么说。UIViewController中添加的协议是一个空协议! - GoZoner
1
哦,非常聪明 :)。 - Frank Schmitt
1
@FrankSchmitt,如果我想让 var detailViewController: DetailViewController 严格成为 UIViewController 的子类,该怎么办?就像 Objective-C 中的声明 @property (strong, nonatomic) UIViewController<DetailViewController> detailViewController; 一样清晰。Swift 能做到这点吗? - liuyaodong
2
这解决了一个问题,但是当你在代码中的某个地方传递了 UIViewControllerInject 时,你不能调用它上面的任何 UIViewController 方法而不进行强制转换(as!)。不幸的是,我认为目前没有办法声明一个变量为符合某个协议的某个类,就像在 ObjC 中那样。当然,你可以在 UIViewControllerInject 协议中声明你需要的所有来自 UIViewController 的方法。 - Charlie Monroe
这在Swift 4中现在是可能的 - Senseful

11

从 Swift 4 开始,您现在可以这样做。

Swift 4 实现了 SE-0156(类和子类型存在性)。

相当于这个 Objective-C 语法:

@property (strong, nonatomic) UIViewController<DetailViewController> * detailViewController;

现在在Swift 4中是这样的:

var detailViewController: UIViewController & DetailViewController

基本上,您可以定义一个类,使变量符合该类的规定,并实现 N 个协议。有关更详细的信息,请参阅链接文档。


2
另一种方法是为适当的UIKit视图控制器引入中间基类,该基类实现协议:
class MyUIViewControler : UIViewController, DetailViewController ...
class MyUITableViewController : UITableViewController, DetailViewController ...

然后你从这些视图控制器中继承你的视图控制器,而不是UIKit中的视图控制器。

这也不是很自然,但它不会像GoZoner建议的那样强制要求所有的UIViewControllers都满足UIViewControllerInject协议。


我会选择这种方式,但我不确定如何声明一个既可以是 MyUIViewController 类型又可以是 MyUITableViewController 类型的属性。 - Frank Schmitt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接