Как объявить переменную, которая имеет тип и реализует протокол?
В моем приложении есть протокол для контроллеров подробных представлений, указав, что они должны иметь свойство 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.