user3906135
user3906135

Reputation: 91

Comparing protocol references

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

Upvotes: 9

Views: 5734

Answers (3)

Steve Heffern
Steve Heffern

Reputation: 97

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.

Upvotes: 0

Oleg Gordeev
Oleg Gordeev

Reputation: 582

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.

Upvotes: 21

jstn
jstn

Reputation: 2336

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
}

Upvotes: 2

Related Questions