作为数组类型和功能参数的协议在快速中的应用

我想创建一个类,它可以存储符合特定协议的对象。对象应该存储在类型化数组中。根据 Swift 文档协议,可以将其作为类型使用:

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

  • 作为函数、方法或初始值设定项中的参数类型或返回类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中项的类型

然而,下面的代码会产生编译器错误:

协议‘ Some Protocol’只能作为通用约束使用,因为它具有 Self 或相关的类型要求

你要怎么解决这个问题:

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)
}
}
}
35897 次浏览

您希望创建一个泛型类,类型约束要求与它一起使用的类符合 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 中遇到了一个协议问题的变体,而目前还没有好的解决方案。

另见 扩展 Array 以检查它是否在 Swift 中排序?,它包含了如何解决这个问题的建议,这些建议可能适合你的具体问题(你的问题非常一般,也许你可以用这些答案找到一个解决方法)。

我发现的有限的解决方案是将协议标记为只有类的协议。这将允许您使用’= = =’运算符比较对象。 我知道这不适用于 structs 等,但是在我的例子中已经足够好了。

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

在 Swift 中有一类特殊的协议,它不提供与实现它的类型相比的多态性。这些协议在其定义中使用 Selfassociatedtype关键字(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($0.x) } // 1 3

我认为你的主要目的是保存符合某种协议的对象集合,添加到这个集合中,然后从中删除。这是您的客户端“ Some Class”中提到的功能。等价继承需要自我,而这对于此功能是不需要的。我们可以使用“ index”函数在 Obj-C 中的数组中实现这一点,该函数可以采用自定义比较器,但 Swift 不支持这一点。因此,最简单的解决方案是使用字典而不是数组,如下面的代码所示。我已经提供了 getElements () ,它将返回您想要的协议数组。因此,任何使用 SomClass 的人都不会知道实现使用了字典。

因为在任何情况下,你都需要一些特殊的属性来区分你的对象,我假设它是“名字”。在创建新的 Some Protocol 实例时,请确保 do element.name = “ foo”。如果未设置名称,仍然可以创建实例,但是不会将其添加到集合中,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)
}
}

我在那篇博文中找到了一个 没有纯粹的 Swift 解决方案: Http://blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/

诀窍是遵循 NSObjectProtocol,因为它引入了 isEqual()。 因此,您可以编写自己的函数来查找元素并删除它,而不是使用 Equatable协议及其 ==的默认用法。

以下是 find(array, element) -> Int?函数的实现:

protocol SomeProtocol: NSObjectProtocol {


}


func find(protocols: [SomeProtocol], element: SomeProtocol) -> Int? {
for (index, object) in protocols.enumerated() {
if (object.isEqual(element)) {
return index
}
}


return nil
}

注意: 在这种情况下,符合 SomeProtocol的对象必须从 NSObject继承。

Swift 5.7/Xcode 14开始,这个问题可以通过 any优雅地解决。

protocol SomeProtocol: Equatable {
func bla()
}


class SomeClass {
var protocols = [any SomeProtocol]()
    

func addElement(element: any SomeProtocol) {
protocols.append(element)
}
    

func removeElement(element: any SomeProtocol) {
if let index = find(protocols, element) {
protocols.remove(at: index)
}
}
}