Использование протоколов в качестве типов массивов и параметров функций в swift
Я хочу создать класс, который может хранить объекты, соответствующие определенному протоколу. Объекты должны храниться в типизированном массиве. Согласно документации Swift протоколы могут использоваться в виде типов:
поскольку это тип, вы можете использовать протокол во многих местах, где разрешены другие типы, в том числе:
- в качестве типа параметра или возвращаемого типа в функции, методе или инициализаторе
- тип константы, переменная или свойство
- как тип элементов в массиве, словаре или другом контейнере
однако следующее создает ошибки компилятора:
протокол 'SomeProtocol' может использоваться только в качестве общего ограничения, поскольку он имеет собственные или связанные требования к типу
Как вы должны решить эту проблему:
protocol SomeProtocol: Equatable {
func bla()
}
class SomeClass {
var protocols = [SomeProtocol]()
func addElement(element: SomeProtocol) {
self.protocols.append(element)
}
func removeElement(element: SomeProtocol) {
if let index = find(self.protocols, element) {
self.protocols.removeAtIndex(index)
}
}
}
6 ответов:
вы попали в вариант проблемы с протоколами в Swift, для которого пока не существует хорошего решения.
см. также расширение массива, чтобы проверить, если он отсортирован в Swift?, Он содержит предложения о том, как обойти его, которые могут быть пригодны для вашей конкретной проблемы (ваш вопрос очень общий, возможно, вы можете найти обходной путь, используя эти ответы).
вы хотите создать универсальный класс с ограничением типа, которое требует, чтобы классы, используемые с ним, соответствовали
SomeProtocol, например:class SomeClass<T: SomeProtocol> { typealias ElementType = T var protocols = [ElementType]() func addElement(element: ElementType) { self.protocols.append(element) } func removeElement(element: ElementType) { if let index = find(self.protocols, element) { self.protocols.removeAtIndex(index) } } }
в Swift существует специальный класс протоколов, который не обеспечивает полиморфизм над типами, которые его реализуют. Такие протоколы используют
Selfилиassociatedtypeключевые слова в их определениях (иEquatable- один из них).в некоторых случаях можно использовать стираемую по типу оболочку, чтобы сделать вашу коллекцию гомоморфной. Ниже приведен пример.
// This protocol doesn't provide polymorphism over the types which implement it. protocol X: Equatable { var x: Int { get } } // We can't use such protocols as types, only as generic-constraints. func ==<T: X>(a: T, b: T) -> Bool { return a.x == b.x } // A type-erased wrapper can help overcome this limitation in some cases. struct AnyX { private let _x: () -> Int var x: Int { return _x() } init<T: X>(_ some: T) { _x = { some.x } } } // Usage Example struct XY: X { var x: Int var y: Int } struct XZ: X { var x: Int var z: Int } let xy = XY(x: 1, y: 2) let xz = XZ(x: 3, z: 4) //let xs = [xy, xz] // error let xs = [AnyX(xy), AnyX(xz)] xs.forEach { print(.x) } // 1 3
ограниченное решение, которое я нашел, состоит в том, чтобы пометить протокол как протокол только для класса. Это позволит вам сравнивать объекты с помощью оператора==='. Я понимаю, что это не будет работать для структур и т. д. но в моем случае этого было достаточно.
protocol SomeProtocol: class { func bla() } class SomeClass { var protocols = [SomeProtocol]() func addElement(element: SomeProtocol) { self.protocols.append(element) } func removeElement(element: SomeProtocol) { for i in 0...protocols.count { if protocols[i] === element { protocols.removeAtIndex(i) return } } } }
решение очень простое:
protocol SomeProtocol { func bla() } class SomeClass { init() {} var protocols = [SomeProtocol]() func addElement<T: SomeProtocol where T: Equatable>(element: T) { protocols.append(element) } func removeElement<T: SomeProtocol where T: Equatable>(element: T) { protocols = protocols.filter { if let e = as? T where e == element { return false } return true } } }
Я так понимаю, что ваша главная цель-провести коллекцию объектов, соответствующих какому-то протоколу, добавить в эту коллекцию и удалить из нее. Это функциональность, как указано в вашем клиенте, "SomeClass". Равновеликое наследование требует self, и это не требуется для этой функции. Мы могли бы сделать эту работу в массивах в Obj-C с помощью функции "index", которая может принимать пользовательский компаратор, но это не поддерживается в Swift. Таким образом, самое простое решение-использовать словарь вместо массив, как показано в коде ниже. Я предоставил getElements (), который вернет вам массив протоколов, который вы хотели. Поэтому любой, кто использует SomeClass, даже не будет знать, что для реализации используется словарь.
поскольку в любом случае вам понадобится какое-то отличительное свойство для разделения ваших объектов, я предположил, что это "имя". Пожалуйста, убедитесь, что ваш сделать element.name = " foo " при создании нового экземпляра SomeProtocol. Если имя не задано, вы все равно можете создать экземпляр, но он не будет добавлен в коллекцию и addElement() вернет "false".
protocol SomeProtocol { var name:String? {get set} // Since elements need to distinguished, //we will assume it is by name in this example. func bla() } class SomeClass { //var protocols = [SomeProtocol]() //find is not supported in 2.0, indexOf if // There is an Obj-C function index, that find element using custom comparator such as the one below, not available in Swift /* static func compareProtocols(one:SomeProtocol, toTheOther:SomeProtocol)->Bool { if (one.name == nil) {return false} if(toTheOther.name == nil) {return false} if(one.name == toTheOther.name!) {return true} return false } */ //The best choice here is to use dictionary var protocols = [String:SomeProtocol]() func addElement(element: SomeProtocol) -> Bool { //self.protocols.append(element) if let index = element.name { protocols[index] = element return true } return false } func removeElement(element: SomeProtocol) { //if let index = find(self.protocols, element) { // find not suported in Swift 2.0 if let index = element.name { protocols.removeValueForKey(index) } } func getElements() -> [SomeProtocol] { return Array(protocols.values) } }
Comments