Как объявить переменную, которая имеет тип и реализует протокол?

В моем приложении есть протокол для контроллеров подробных представлений, указав, что они должны иметь свойство viewModel:

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

У меня также есть несколько разных классов, которые реализуют протокол:

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

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

Моему контроллеру главного представления требуется свойство, которое может быть установлено для любого подкласса UIViewController, который реализует протокол DetailViewController.

К сожалению, я не могу найти документацию о том, как это сделать. В Objective-C было бы тривиально:

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

Похоже, что в Swift нет синтаксиса для этого. Самое близкое, что я пришел, - объявить родовое определение моего класса:

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

Но потом я получаю сообщение об ошибке: "Класс" MasterViewController "не реализует своих членов, связанных с суперклассом"

Кажется, что это должно быть так же легко сделать в Swift, как и в Objective-C, но я не могу найти ничего, что подсказывает, как я могу это сделать.

Ответ 1

С Swift 4 вы можете сделать это.

Swift 4 реализован SE-0156 (Экземпляры класса и подтипа).

Эквивалент синтаксиса Objective-C:

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

Теперь выглядит так в Swift 4:

var detailViewController: UIViewController & DetailViewController

По существу вы можете определить один класс, которому соответствует переменная, и N количество протоколов, которые оно реализует. Подробнее см. Связанный документ.

Ответ 2

Я думаю, вы можете добраться туда, добавив (пустое) расширение до UIViewController, а затем указав свой атрибут detailViewController, используя составленный протокол пустого расширения и ваш detailViewController. Вот так:

protocol UIViewControllerInject {}
extension UIViewController : UIViewControllerInject {}

Теперь все подклассы UIViewController удовлетворяют протоколу UIViewControllerInject. Тогда просто:

typealias DetailViewControllerComposed = protocol<DetailViewController, UIViewControllerInject>

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

Но это не особенно "естественно".

=== Редактировать, добавить ===

На самом деле, вы можете сделать это немного лучше, если вы определите свой detailViewController, используя мой предложенный UIViewControllerInject. Например:

protocol UIViewControllerInject {}
extension UIViewController : UIViewControllerInject {}

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

и теперь вам не нужно явно что-то компилировать (my DetailViewControllerComposed) и использовать DetailViewController? как тип для detailViewController.

Ответ 3

Другим способом было бы ввести промежуточные базовые классы для соответствующих контроллеров представления UIKit, которые реализуют протокол:

class MyUIViewControler : UIViewController, DetailViewController ...
class MyUITableViewController : UITableViewController, DetailViewController ...

Затем вы наследуете свои контроллеры представлений от этих контроллеров представления, а не UIKit.

Это тоже не является естественным, но это не заставляет все ваши UIViewControllers удовлетворять протоколу UIViewControllerInject, как предлагал GoZoner.