9

I have an array of protocols. now I want to remove an item from the array, by finding the index of the protocol with the array. however, when comparing the protocol object with the items in the array, the compiler warns with:

'Protocol' does not conform to AnyObject

protocol SomeProtocol {}
var list:[SomeProtocol] = []
func add(some:SomeProtocol) { list+=some }
func remove(some:SomeProtocol) {
    var index = -1
    for i in 0...list.count-1 { if [i] === some { index = i } }
    if index >= 0 { list.removeAtIndex(index) }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
user3906135
  • 91
  • 1
  • 2

3 Answers3

21

If you derive only classes for the protocol, you can change protocol definition to:

protocol SomeProtocol: class {}

Then you will be able to use references with this protocol.

John
  • 6,503
  • 3
  • 37
  • 58
Oleg Gordeev
  • 554
  • 4
  • 6
2

First of all, doing add is super easy, just include this function to make it work:

func +=(inout lhs: [SomeProtocol], rhs: SomeProtocol) {
    lhs.append(rhs)
}

Doing remove is a lot trickier because SomeProtocol could apply equally to a class or struct, and only values with class types can be compared with ===.

We could use the == operator instead, but it only takes values that conform to the Equatable protocol, and Equatable can only be used as a generic constraint (so far), otherwise you could use something like protocol<SomeProtocol,Equatable> as your array element type.

If you know for sure that your SomeProtocol will only be applied to classes, consider refactoring your code to work with that class type instead:

protocol SomeProtocol {}
class SomeClass : SomeProtocol {}

var list:[SomeClass] = []

func add(some:SomeClass) {
    list += some
}

func remove(some:SomeClass) {
    list -= some
}

func +=(inout lhs: [SomeClass], rhs: SomeClass) {
    lhs.append(rhs)
}

func -=(inout lhs: [SomeClass], rhs: SomeClass) {
    for (i,v) in enumerate(lhs) {
        if v === rhs {
            lhs.removeAtIndex(i)
            break
        }
    }
}

If you also happen to make SomeClass conform to Equatable the usual array functions will work automatically, and you won't even need to overload += and -=.

Otherwise, if you can't know whether your value will be a class or a struct, it might be better to think about what it means for values of SomeProtocol to be "equal" and require a comparison method:

protocol SomeProtocol {
    func isEqualTo(some: SomeProtocol) -> Bool
}

func -=(inout lhs: [SomeProtocol], rhs: SomeProtocol) {
    for (i,v) in enumerate(lhs) {
        if v.isEqualTo(rhs) {
            lhs.removeAtIndex(i)
            break
        }
    }
}

// add functions work the same as above

Alternatively, you could use your original code and write a global comparison function:

func ===(lhs: SomeProtocol, rhs: SomeProtocol) -> Bool {
    // return something based on the properties of SomeProtocol
}
jstn
  • 2,326
  • 1
  • 17
  • 15
0

I end up using isEqual(to: ) in my protocols to test for instance comparisons:

public protocol fooProtocol {
    ...
    func isEqual(to: fooProtocol) -> Bool
}

public extension fooProtocol {
    func isEqual(to: fooProtocol) -> Bool {
        let ss = self as! NSObject
        let tt = to as! NSObject
        return ss === tt
    }
}

Seems to work for me.

  • I think the cast to NSObject is dangerous, since the protocol may be used for structs. Then the cast would fail. So I would recommend using @Oleg Gordeev answer to make the class-only intend clear. – Minding May 25 '20 at 11:24