在swift中使用协议作为数组types和函数参数

我想创build一个可以存储符合某个协议的对象的类。 对象应该存储在一个types数组中。 根据Swift文档协议可以作为types使用:

因为它是一种types,所以可以在许多其他types允许的地方使用协议,包括:

  • 作为函数,方法或初始值设定项中的参数types或返回types
  • 作为常量,variables或属性的types
  • 作为数组,字典或其他容器中的项目的types

但是,以下生成编译器错误:

协议“SomeProtocol”只能用作通用约束,因为它具有自我或相关types的要求

你应该如何解决这个问题:

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) } } } 

你已经遇到了Swift协议中的一个问题,目前还没有一个好的解决scheme。

另请参阅扩展数组来检查它是否在Swift中sorting? ,它包含了如何解决这个问题的build议,可能适合您的特定问题(您的问题非常普遍,也许您可​​以使用这些答案find解决方法)。

你想创build一个generics类,带有一个types约束,要求与它一起使用的类符合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中有一个特殊的协议类,它没有为实现它的types提供多态性。 这些协议在他们的定义中使用Self或者associatedtype关键字(而Equatable就是其中之一)。

在某些情况下,可以使用删除types的包装来使您的collections同态。 下面是一个例子。

 // 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 ax == bx } // 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($0.x) } // 1 3 

我发现有限的解决scheme是将该协议标记为纯类协议。 这将允许您使用'==='运算符比较对象。 我知道这不适用于结构等,但对我来说这已经够好了。

 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 } } } } 

解决scheme非常简单:

 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 = $0 as? T where e == element { return false } return true } } } 

我认为你的主要目标是保存符合某些协议的对象集合,添加到这个集合中并从中删除。 这是您的客户“SomeClass”中所述的function。 可衡量的inheritance需要自我,这不需要这个function。 我们可以用Obj-C中的数组来完成这个工作,使用“索引”函数可以采用自定义的比较器,但是这在Swift中不被支持。 所以最简单的解决scheme是使用字典而不是数组,如下面的代码所示。 我已经提供了getElements(),它会让你回到你想要的协议数组。 所以任何使用SomeClass的人都不会知道字典是用来实现的。

既然在任何情况下,你都需要一些独特的属性来区分你的objets,我认为它是“名字”。 当你创build一个新的SomeProtocol实例时,请确保你的do element.name =“foo”。 如果名称没有设置,您仍然可以创build实例,但不会将其添加到集合中,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) } }