В Swift я могу явно задать тип переменной, объявив ее следующим образом:
var object: TYPE_NAME
Если мы хотим сделать еще один шаг и объявить переменную, которая соответствует нескольким протоколам, мы можем использовать декларацию protocol
:
var object: protocol<ProtocolOne,ProtocolTwo>//etc
Что делать, если я хотел бы объявить объект, который соответствует одному или нескольким протоколам, а также относится к конкретному типу базового класса? Эквивалент Objective-C будет выглядеть следующим образом:
NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;
В Swift я ожидаю, что он будет выглядеть так:
var object: TYPE_NAME,ProtocolOne//etc
Это дает нам гибкость в том, чтобы иметь дело с реализацией базового типа, а также с добавленным интерфейсом, определенным в протоколе.
Есть ли еще более очевидный способ, которым я мог бы не хватать?
Пример
В качестве примера, скажем, у меня есть UITableViewCell
factory, который отвечает за возврат ячеек, соответствующих протоколу. Мы можем легко настроить общую функцию, которая возвращает ячейки, соответствующие протоколу:
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
в объявлении переменной?
Оправдание
Если вы знакомы с шаблоном Factory, это будет иметь смысл в контексте возможности возврата объектов определенного класса, реализующего определенный интерфейс.
Как и в моем примере, иногда нам нравится определять интерфейсы, которые имеют смысл при применении к определенному объекту. Мой пример ячейки представления таблицы является одним из таких оправданий.
Пока поставляемый тип точно не соответствует указанному интерфейсу, возвращается объект factory, поэтому мне нужна гибкость при взаимодействии как с типом базового класса, так и с объявленным интерфейсом протокола
Ответ 1
В Swift 4 теперь можно объявить переменную, которая является подклассом типа и одновременно реализует один или несколько протоколов.
var myVariable: MyClass & MyProtocol & MySecondProtocol
или как параметр метода:
func shakeEm(controls: [UIControl & Shakeable]) {}
Apple объявила об этом на WWDC 2017 в Сессия 402: Что нового в Swift
Во-вторых, я хочу поговорить о создании классов и протоколов. Так вот Я представил этот шаткий протокол для элемента пользовательского интерфейса, который может дать немного встряхнуть, чтобы привлечь внимание к себе. И я пошел вперед и расширили некоторые классы UIKit, чтобы фактически обеспечить это встряхивание функциональность. И теперь я хочу написать что-то, что кажется простым. я просто хочу написать функцию, которая принимает кучу элементов управления, которые дрожащие и трясущиеся те, которые позволяют привлечь внимание к их. Какой тип я могу написать здесь в этом массиве? Это на самом деле разочарование и сложность. Итак, я могу попытаться использовать элемент управления пользовательского интерфейса. Но нет все элементы управления пользовательским интерфейсом являются шаткой в этой игре. Я мог бы попытаться поколебать, но не все shakables являются элементами управления пользовательского интерфейса. И на самом деле нет хорошего способа представляют это в Swift 3. Swift 4 вводит понятие составления класс с любым количеством протоколов.
Ответ 2
Вы не можете объявить переменную как
var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...
и не объявлять возвращаемый тип функции, например
func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }
Вы можете объявить в качестве параметра функции, как это, но это в основном ускорение.
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>
Ответ 3
К сожалению, 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 структуры и всему, что вам нужно для протокола, вы должны ссылаться на .protocol.
Для экземпляра:
class SomeClass {
let mySpecialViewController: VCWithSomeProtocol
init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
}
}
Теперь, когда вам нужен mySpecialViewController, чтобы сделать что-либо, связанное с UIViewController, вы просто ссылаетесь на mySpecialViewController.viewController, и всякий раз, когда вам это нужно, чтобы выполнить некоторую функцию протокола, вы ссылаетесь на mySpecialViewController.protocol.
Скорее всего, Swift 4 позволит нам объявить объект с подключенными к нему протоколами в будущем. Но пока это работает.
Надеюсь, это поможет!
Ответ 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 Общие функции для несовместимых типов с дополнительными примерами.
Это дает нам гибкость в том, чтобы иметь дело с реализацией базового типа, а также с добавленным интерфейсом, определенным в протоколе.
Есть ли еще более очевидный способ, которым я мог бы не хватать?
Принятие протокола будет делать именно это, сделать объект привязанным к данному протоколу. Однако следует помнить о неблагоприятной стороне, что переменная данного типа протокола ничего не знает вне протокола. Но это можно обойти, указав протокол, который имеет все необходимые методы/переменные/...
Пока поставляемый тип точно не соответствует указанному интерфейсу, возвращается объект factory, поэтому мне нужна гибкость при взаимодействии как с типом базового класса, так и с объявленным интерфейсом протокола
Если вы хотите, чтобы для универсального метода переменная соответствовала как типу протокола, так и базовому классу, вам может быть не повезло. Но похоже, что вам необходимо определить протокол достаточно широко, чтобы иметь необходимые методы соответствия и в то же время достаточно узкие, чтобы иметь возможность использовать его для базовых классов без большой работы (т.е. Просто объявить, что класс соответствует протокол).
Ответ 5
У меня когда-то была аналогичная ситуация при попытке связать мои общие подключения для взаимодействия в Storyboards (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.k.a.) через последнее (вычисленное свойство), я эффективно отфильтровываю любые объекты, которые не соответствуют протоколу. Теперь я могу просто использовать необязательную цепочку для безопасного вызова объекту делегата независимо от того, реализует ли он протокол или даже существует.
Чтобы применить это к вашей ситуации, вы можете иметь общедоступный ivar типа "YourBaseClass"? (в отличие от AnyObject) и использовать частное вычисляемое свойство для обеспечения соответствия протокола. FWIW.