В Swift, как я могу объявить переменную определенного типа, которая соответствует одному или нескольким протоколам?

в Swift я могу явно установить тип переменной, объявив ее следующим образом:

var object: TYPE_NAME

если мы хотим сделать еще один шаг и объявить переменную, соответствующую нескольким протоколам, мы можем использовать protocol декларативный:

var object: protocol<ProtocolOne,ProtocolTwo>//etc

что делать, если я хотел бы объявить объект, который соответствует одному или нескольким протоколам, а также имеет определенный тип базового класса? Эквивалент Objective-C будет выглядеть так:

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;

В Swift I ожидал бы, что это будет выглядеть так:

var object: TYPE_NAME,ProtocolOne//etc

это дает нам гибкость иметь дело с реализацией базового типа, а также добавленного интерфейса, определенного в протоколе.

есть ли другой более очевидный способ, которым я могу отсутствовать?

пример

в качестве примера, скажем, у меня UITableViewCell фабрика, которая отвечает за возврат ячеек, соответствующих протоколу. Мы можем легко настроить общую функцию, которая возвращает ячейки, соответствующие протоколу:

class CellFactory {
    class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
        //etc
    }
}

позже я хочу удалить эти ячейки, используя как тип, так и протокол

var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell

это возвращает ошибку, поскольку ячейка представления таблицы не соответствует протоколу...

Я хотел бы иметь возможность указать, что ячейка является UITableViewCell и соответствует MyProtocol в объявлении переменной?

обоснование

если вы знакомы с Шаблон Фабрики это имело бы смысл в контексте возможности возвращать объекты определенного класса, реализующие определенный интерфейс.

как и в моем примере, иногда нам нравится определять интерфейсы, которые имеют смысл при применении к конкретному объекту. Одним из таких обоснований является мой пример ячейки представления таблицы.

в то время как поставляемый тип точно не соответствует упомянутому интерфейсу, объект, который возвращает фабрика, делает и так Я хотел бы гибкость во взаимодействии как с типом базового класса, так и с объявленным интерфейсом протокола

5 ответов


в Swift 4 Теперь можно объявить переменную, которая является подклассом типа и реализует один или несколько протоколов одновременно.

var myVariable: MyClass & MyProtocol & MySecondProtocol

или как параметр метода:

func shakeEm(controls: [UIControl & Shakeable]) {}

Apple объявила об этом на WWDC 2017 в сессия 402: что нового в Swift

во-вторых, я хочу поговорить о создании классов и протоколы. Так вот Я представил этот shakable протокол для элемента UI, который может дать ля небольшой эффект встряхивания, чтобы привлечь к себе внимание. И я пошел вперед. и расширил некоторые из классов UIKit, чтобы фактически обеспечить эту встряску функциональность. А теперь я хочу написать что-нибудь простое. Я просто хочу написать функцию, которая принимает кучу элементов управления, shakable и встряхивает те, которые позволяют привлечь внимание к их. Какой тип я могу написать здесь в этом массиве? Это на самом деле неприятно и сложно. Итак, я могу попробовать использовать элемент управления UI. Но не все пользовательского интерфейса управление shakable в этой игре. Я мог бы попробовать shakable, но не все shakables являются элементами управления UI. И на самом деле нет хорошего способа представлять это в Swift 3. Swift 4 вводит понятие составления класс с любым количеством протоколов.


вы не можете объявить переменную как

var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...

ни объявить функцию возвращаемого типа, как

func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }

вы можете объявить в качестве параметра функции, как это, но это в основном up-casting.

func someFunc<T:Base where T:protocol<MyProtocol1,MyProtocol2>>(val:T) {
    // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2`
}

class SubClass:BaseClass, MyProtocol1, MyProtocol2 {
   //...
}

let val = SubClass()
someFunc(val)

на данный момент все, что вы можете сделать, это:

class CellFactory {
    class func createCellForItem(item: SpecialItem) -> UITableViewCell {
        return ... // any UITableViewCell subclass
    }
}

let cell = CellFactory.createCellForItem(special)
if let asProtocol = cell as? protocol<MyProtocol1,MyProtocol2> {
    asProtocol.protocolMethod()
    cell.cellMethod()
}

С этим, технически cell идентичен asProtocol.

но, что касается компилятора,cell есть интерфейс UITableViewCell, а asProtocol есть только протоколы взаимодействие. Итак, когда вы хотите позвонить UITableViewCellметоды, вы должны использовать cell переменной. Если вы хотите вызвать метод протоколов, используйте asProtocol переменной.

если вы уверены, что ячейка соответствует протоколам, вам не нужно использовать if let ... as? ... {}. например:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>

к сожалению, Swift не поддерживает соответствие протокола уровня объекта. Тем не менее, есть несколько неудобная работа, которая может служить вашим целям.

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

    init<T: UIViewController>(vc: T) where T: SomeProtocol {
        self.protocol = vc
        self.viewController = vc
    }
}

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

Например:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

   init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
       self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
   }
}

теперь в любое время вам нужен mySpecialViewController сделайте все, что связано с UIViewController, вы просто ссылаетесь на mySpecialViewController.viewController и всякий раз, когда вам нужно это сделать какую-то функцию протокола, вы ссылаетесь на mySpecialViewController.протокол.

надеюсь, Swift 4 позволит нам объявить объект с прикрепленными к нему протоколами в будущем. Но пока это работает.

надеюсь, что это помогает!


EDIT: Я ошибся, но если кто-то еще читать это недоразумение, как я, я оставлю этот ответ здесь. ОП спросил о проверке соответствия протокола объекта данного подкласса, и это еще одна история, как показывает принятый ответ. Этот ответ говорит о соответствии протокола для базового класса.

возможно, я ошибаюсь, но вы не говорите о добавлении соответствие протокола UITableCellView класса? В этом случае протокол распространяется на базовый класс, а не на объект. См. документацию Apple по объявление принятия протокола с расширением который в вашем случае был бы чем-то вроде:

extension UITableCellView : ProtocolOne {}

// Or alternatively if you need to add a method, protocolMethod()
extension UITableCellView : ProcotolTwo {
   func protocolTwoMethod() -> String {
     return "Compliant method"
   }
}

В дополнение к уже упомянутой документации Swift, также см. статью Nate Cooks общие функции для несовместимых типов дополнительные примеры.

Это дает нам гибкость мочь общаться с реализация базового типа, а также добавленного интерфейса, определенного в протоколе.

есть ли другой более очевидный способ, которым я могу отсутствовать?

принятие протокола сделает именно это, заставит объект придерживаться данного протокола. Однако имейте в виду неблагоприятную сторону, что переменная данного типа протокола делает не знать что-нибудь за пределами протокола. Но это можно обойти, определив протокол, который имеет все необходимые методы / переменные/...

хотя поставляемый тип точно не соответствует упомянутому интерфейсу, объект, возвращаемый фабрикой, делает это, и поэтому я хотел бы гибкость во взаимодействии как с типом базового класса, так и с объявленным интерфейсом протокола

Если вы хотите, чтобы общий метод, переменная соответствовала как протоколу, так и типам базового класса, вам может не повезти. Но похоже, что вам нужно определить протокол wide достаточно, чтобы иметь необходимые методы соответствия, и в то же время достаточно узкие, чтобы иметь возможность принять его к базовым классам без слишком большой работы (т. е. просто объявить, что класс соответствует протоколу).


У меня когда-то была аналогичная ситуация при попытке связать мои общие соединения interactor в раскадровках (IB не позволит вам подключать выходы к протоколам, только экземпляры объектов), которые я обошел, просто маскируя базовый класс public ivar с частным вычисляемым свойством. Хотя это не мешает кому-то делать незаконные назначения как таковые, это обеспечивает удобный способ безопасно предотвратить любое нежелательное взаимодействие с несоответствующим экземпляром во время выполнения. (т. е. предотвратить вызов методов делегата для объектов, которые не соответствуют протоколу.)

пример:

@objc protocol SomeInteractorInputProtocol {
    func getSomeString()
}

@objc protocol SomeInteractorOutputProtocol {
    optional func receiveSomeString(value:String)
}

@objc class SomeInteractor: NSObject, SomeInteractorInputProtocol {

    @IBOutlet var outputReceiver : AnyObject? = nil

    private var protocolOutputReceiver : SomeInteractorOutputProtocol? {
        get { return self.outputReceiver as? SomeInteractorOutputProtocol }
    }

    func getSomeString() {
        let aString = "This is some string."
        self.protocolOutputReceiver?.receiveSomeString?(aString)
    }
}

" outputReceiver "объявлен необязательным, как и частный"protocolOutputReceiver". Всегда обращаясь к outputReceiver (a.к. a. делегат) через последнее (вычисляемое свойство) я эффективно отфильтровываю любые объекты, которые не соответствуют протоколу. Теперь я могу просто использовать необязательную цепочку для безопасного вызова объекта delegate независимо от того, реализует ли он протокол или даже существует.

чтобы применить это к вашей ситуации, вы можете иметь публичный ivar типа " YourBaseClass?"(в отличие от AnyObject) и использовать частное вычисляемое свойство для обеспечения соответствия протоколу. Чистки рядов.