快速将协议用作数组类型和函数参数


136

我想创建一个类,可以存储符合特定协议的对象。对象应存储在类型数组中。根据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)
        }
    }
}

2
在Swift中,有一类特殊的协议不能在实现它的类型上提供多态性。此类协议在其定义中使用Self或相关类型(而Equatable是其中之一)。在某些情况下,可以使用类型擦除的包装器使您的集合同构。例如看这里
潜水员

Answers:


48

您在Swift中遇到了协议问题的一个变体,目前尚无好的解决方案。

另请参见扩展数组以检查它是否在Swift中排序?,其中包含有关如何解决它的建议,这些建议可能适合您的特定问题(您的问题非常笼统,也许您可​​以使用这些答案找到解决方法)。


1
我认为这是目前正确的答案。Nate的解决方案有效,但不能完全解决我的问题。
snod

32

您想要创建一个具有类型约束的通用类,该通用类要求与其一起使用的类必须符合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)
        }
    }
}

您将如何实例化该类的对象?
snod 2014年

嗯......这样把您锁定在使用单一类型的符合SomeProtocol-let protocolGroup: SomeClass<MyMemberClass> = SomeClass()
内特库克

这样,您只能将类的对象添加MyMemberClass到数组吗?
snod 2014年

let foo = SomeClass<MyMemberClass>()
DarkDust 2014年

@snod是的,这不是您想要的。问题是Equatable一致性-没有一致性,您就可以使用确切的代码。可能会提交错误/功能请求?
内特·库克

15

在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

12

我发现的有限解决方案是将协议标记为仅类协议。这将允许您使用'==='运算符比较对象。我知道这不适用于结构等,但是就我而言,这已经足够了。

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

}

protocols如果addElement同一对象被多次调用,这是否不允许中的重复条目?
汤姆·哈灵顿

是的,快速数组可能包含重复的条目。如果您认为这可能在代码中发生,请使用Set而不是array,或者确保该数组已经不包含该对象。
almas

removeElement()如果希望避免重复,可以在附加新元素之前进行调用。
Georgios

我的意思是您如何控制阵列悬而未决,对吗?谢谢您的回答
Reimond Hill,

9

解决方案非常简单:

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

4
您错过了重要的事情:OP希望协议继承Equatable协议。这有很大的不同。
潜水员

@werediver我不这么认为。他想将符合的对象存储SomeProtocol在类型数组中。Equatable只有从数组中删除元素时才需要一致性。我的解决方案是@almas解决方案的改进版本,因为它可以与任何符合Equatable协议的Swift类型一起使用。
bzz

2

我认为您的主要目的是保存符合某些协议的对象的集合,将其添加到该集合中并从中删除。这是客户端“ SomeClass”中所述的功能。平等继承需要自我,而此功能不需要。我们可以使用“索引”函数在Obj-C的数组中完成这项工作,该函数可以使用自定义比较器,但是Swift不支持此功能。因此,最简单的解决方案是使用字典而不是数组,如下面的代码所示。我提供了getElements(),它将为您提供所需的协议数组。因此,使用SomeClass的任何人甚至都不知道使用字典来实现。

因为无论如何,您都需要一些区别属性来分隔您的objets,所以我假设它是“名称”。创建新的SomeProtocol实例时,请确保您的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)
    }
}

0

我在该博客文章中找到了一种纯粹的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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.