2017-02-07 4 views
4

TL; DRСоздать массив объектов, который реализует конкретный протокол

Я ищу типа массива (var array = [TheTypeImLookingFor]()), как «все объекты, которые подклассы UIViewControllerи реализует протокол MyProtocol.

Объяснение

Я строю вид зрения мастера с точки зрения контейнера и встроенными представлениями ребенка (контроллер). Нет проблем, это будет работать так долго, поскольку у меня есть только один тип контроллеров дочерних представлений base.

Благодаря содержанию экранов, у меня сейчас куча зрения контроллеров типа MyTableViewController, который является подклассом UITableViewController и другие контроллеры имеют вид, что регулярные UIViewControllers в качестве основы.

Все контроллеры вида имеют одну общую черту. Свойство данных по умолчанию myData: MyObject.

Я создал протокол MyProtocol, который содержит это свойство.

Теперь я должен объединить все эти контроллеры представлений в один массив, чтобы использовать его в качестве шагов мастера. Пока мне нужно только получить доступ к методам контроллера представления (элементы массива - это тип UIViewController) Я могу использовать var viewControllers = [UIViewController](), или если я хочу получить доступ только к свойству myData, я изменяю тип элемента массива на MyObject.

Но проблема в том, что мне нужно получить доступ к методам из UIViewController и из протокола.

Вот почему я ищу типа массива, как «все объекты, которые подклассы UIViewControllerи реализует протокол MyProtocol.

Я пробовал:

  • var viewControllers = [UIViewController: MyProtocol]() // является Dict
  • `вар viewControllers = UIViewController, где MyProtocol
  • ` вар viewControllers = UIViewController.conforms (к: MyProtocol)
  • ...

Но ничего не работает должным образом.

+0

Элементы массива * have * должны быть введены как 'UIViewController', так и' MyProtocol'? В противном случае вы можете просто использовать методы из 'UIViewController', которые вам требуются в' MyProtocol' (или создать для них отдельный протокол и использовать протокол). – Hamish

+0

Да, они должны быть обоими. Может быть, моя идея реализации неверна. – Tobonaut

ответ

0

Почему бы не просто создать:

Почему бы не создать:

class ObservingViewController : UIViewController, MyProtocol { 


} 

var viewControllers : [ObservingViewController] = [] 
+0

Я считаю, что OP хочет, чтобы элементы этого массива знали, что они также являются объектами подкласса 'UIViewController', что не будет иметь дело с работой только с типизированными объектами протокола. – dfri

+0

Да, @dfri. Это моя проблема. – Tobonaut

+0

Один из классов OP наследует от 'UITableViewController', поэтому это не сработает. – Hamish

2

Насколько мне известно, в настоящее время нет способа напечатать что-нибудь так, что он описывает все, что наследуется от данного класса и соответствует данному протоколу.

Одним из возможных способов взлома является просто создать тип обертки, чтобы выполнить приведение типов к вам в случае, если вам нужно обработать экземпляр как MyProtocol.

struct MyProtocolViewController { 

    let base: UIViewController 

    init<T : UIViewController>(_ base: T) where T : MyProtocol { 
     self.base = base 
    } 

    func asMyProtocol() -> MyProtocol { 
     return base as! MyProtocol 
    } 
} 

Теперь вы можете создать [MyProtocolViewController], и может либо рассматривать элемент как UIViewController или MyProtocol.

// given that ViewController and AnotherViewController conform to MyProtocol. 
let viewControllers = [MyProtocolViewController(ViewController()), 
         MyProtocolViewController(AnotherViewController())] 

for viewController in viewControllers { 
    print(viewController.asMyProtocol().myData) 
    print(viewController.base.prefersStatusBarHidden) 
} 
+0

Спасибо большое, ребята! Вы очень помогли мне с идеями. На данный момент я работаю с двумя массивами, для одного из объектов без объекта «UIViewController» и одного для объектов с литыми (для протокола). Это уродливо и глупо, но в данный момент оно работает. – Tobonaut

+0

@Tobonaut Я бы не рекомендовал использовать параллельные массивы для этого, хотя я признаю, что, похоже, не существует «идеального» решения для него. – Hamish

+0

Хм, хорошо. Как вы думаете, у меня есть неправильные «мысли» об этом? Я бывший разработчик Java EE, и, возможно, я использую неправильный шаблон. – Tobonaut

0

Вы также можете создать , который определяет все UIViewController функций, которые вам нужны. Убедитесь, что вы копируете подпись метода, иначе вам придется снова выполнять функции.

protocol UIViewControllerInteractions { 
    //copy the signature from the methods you want to interact with here, e.g. 
    var title: String? { get set } 
} 

Затем вы можете расширить существующий протокол.

protocol MyProtocol: UIViewControllerInteractions { } 

Или создать новый протокол, который расширяет UIViewControllerInteractions и MyProtocol.

protocol MyProtocolViewController: UIViewControllerInteractions, MyProtocol { } 

Теперь, когда вы расширяете ваш SubclassUIViewController, вы только должны добавить свой myData, потому что методы в UIViewControllerInteractions уже реализованы UIViewController (именно поэтому мы копировали метод подписи)

class SubclassUIViewController: MyProtocol { 
    var myData ... 
} 

Теперь вы можете получить arrayMyProtocol или MyProtocolViewController, а также вызвать методы, определенные в UIViewControllerInteractions, которые вызовут методы UIViewController.

var viewController: [MyProtocol] = [...] 
viewController.forEach { (vc) in 
    print(vc.myData) 
    print(vc.title) 
} 
+0

[OP говорит] (http://stackoverflow.com/questions/42090028/create-an-array-of-objects-that-implements-a-specific-protocol#comment71351002_42090028), они должны иметь возможность использовать элементы массива как 'UIViewController' набрал экземпляры - у меня тоже такая мысль :) – Hamish

+0

Я пропустил эту часть. Спасибо :) Может быть, это помогает кому-то с подобной проблемой – Yannick

1

Вы можете использовать состав протокола с протоколом заполнителя для класса:

protocol UIViewControllerClass {} 
extension UIViewController: UIViewControllerClass {} 

protocol MyProtocol:class {} 

class MySpecialVC:UIViewController,MyProtocol {}  

var viewControllers = [UIViewControllerClass & MyProtocol]() 

viewControllers.append(MySpecialVC()) 

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

extension MyProtocol where Self: UIViewControllerClass 
{ 
    var vc:UIViewController { return self as! UIViewController } 
} 

// accessing the view controller's methods would then only require insertion of a property name. 
viewControllers.first!.vc.view 

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

0

У меня была аналогичная проблема и она была решена с использованием базового класса. Представьте себе массив как:

var viewControllers: [MapViewController] 

, которые все должны продлить от UIViewController и реализовать следующий протокол:

protocol MapViewControllerDelegate { 
    func zoomToUser() 
} 

Тогда я объявил базовый класс, как:

class MapViewController: UIViewController { 
    var delegate: MapViewControllerDelegate? 
} 

Внимание: этот класс не реализует вышеуказанный протокол, но обладает свойством, которое обеспечивает требуемую функциональность. Следующий шаг состоит в определении одной из UIViewController, которые будут добавлены в массив:

class GoogleMapsViewController: MapViewController { 
    override func viewDidLoad() { 
     super.viewDidLoad() 
     delegate = self 
    } 
} 

extension GoogleMapsViewController: MapViewControllerDelegate { 
    func zoomToUser() { 
     // Place custom google maps code here 
    } 
} 

важная часть находится в методе viewDidLoad. Контроллер представления назначает себя как делегат.

Использование:

let googleMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "GoogleMapsViewController") as! GoogleMapsViewController 
let mapboxMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MapboxMapsViewController") as! MapboxMapsViewController 
let mapViewControllers: [MapViewController] = [googleMapsViewController, mapboxViewController] 
for mapVC in mapViewControllers { 
    mapVC.delegate?.zoomToUser() 
} 

Преимущества:

  • MapViewController, как абстрактный класс и если я изменить MapViewControllerDelegate силы компилятора мне осуществить изменения в GoogleMapsViewController и в MapboxMapsViewController.
  • Если мне нужен второй протокол, я мог бы просто реализовать второе свойство делегата.
  • Никакой тип кастинга не нужен, как в других ответах. Каждый UIViewController по-прежнему является UIViewController и предоставляет все его методы.
Смежные вопросы