23

I would like to initialize a Set with values corresponding to the Hashable protocol and a custom protocol.

I tried :

protocol CustomProtocol: Hashable {}

let set = Set<CustomProtocol>()

But Xcode complains :

Using 'CustomProtocol' as a concrete type conforming to protocol 'Hashable' is not supported

How can I achieve that ?

Thanks in advance.

GaétanZ
  • 4,870
  • 1
  • 23
  • 30
  • Objects in are already required to conform to Hashable. – Abizern Oct 17 '15 at 17:25
  • If CustomProtocol does not conform to Hashable, Xcode complains about CustomProtocol not conforming to it. It looks like I'm missing something. – GaétanZ Oct 17 '15 at 17:36

2 Answers2

10

The immediate reason why you can't do what you want to do is that Hashable is a generic protocol. Thus it — or a protocol that derives from it — cannot be used as a Set's element type. A generic type can used only as a constraint in another generic. You will notice that you can't declare a Set<Hashable> either, even though a set's element type must conform to Hashable.

The simplest approach is to make, not a set of protocols, but a set of some object type. For example, if S is a struct that conforms to CustomProtocol (because it conforms to Hashable plus whatever else CustomProtocol entails), you can declare a set of S.

Example:

protocol CustomProtocol: Hashable {

}

func ==(lhs:S,rhs:S) -> Bool {
    return lhs.name == rhs.name
}

struct S : CustomProtocol {
    var name : String
    var hashValue : Int { return name.hashValue }
}

let set = Set<S>()

If the problem you're trying to solve is that you want a collection of mixed types which are nevertheless in some way equatable to one another, then that is the very same problem solved by protocol extensions, as explained by the discussion in the Protocol-Oriented WWDC 2015 video.

But it would be simpler just to make all your types classes that derive from NSObject. You can still make them adopt some secondary protocol, of course, but the set won't be defined as a set of that protocol but of NSObject.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks. But I want to insert in my set objects from different classes, not a single one. That's why I wan't to create a "set of protocols". – GaétanZ Oct 17 '15 at 18:02
  • See my revised answer. – matt Oct 17 '15 at 18:02
  • The easiest way of course is just to make all your classes derive from NSObject, as you've already been advised. – matt Oct 17 '15 at 18:02
  • I was looking for an answer which sounds like a protocol extension. I watched the video when it came out but I can't figure how to use that approach in that case. For now, an Array would do the job. I will watch it again later. (I love your books by the way, I'm glad your answer to one of my questions) – GaétanZ Oct 17 '15 at 18:09
  • In the video, the problem is to define equatable for a protocol, which is what you are facing with your CustomProtocol - how do we know when two CustomProtocol adopters are `==` to one another? We cannot implement Hashable without a definition of `==` that answers this question. He solves this with a protocol extension constrained `where Self:Equatable` that defines an extra equating function. In this way you will be able to guarantee that `==` is meaningful for any pair of your Set members. I won't repeat his code because what you want to do is exactly what he does. – matt Oct 17 '15 at 18:16
  • BTW if an array is acceptable then just use it. It will make things a lot simpler. Unfortunately you have not explained what the underlying problem is that you are _really_ trying to solve. I think I answered the question you did ask, namely why you're getting this error. – matt Oct 17 '15 at 20:07
  • No no I do need a Set, but only for one method. That's not my main problem right now. You answered right. I will look at this extension protocol later. – GaétanZ Oct 17 '15 at 20:10
  • Cool, but it would be great to know what the underlying problem really is here. – matt Oct 17 '15 at 20:11
7

In Swift 3, one solution is to use the AnyHashable structure.

For instance, to create a Observers/Observable pattern, we can do :

protocol Observer {
    func observableDidSomething(_ observable: Observable)
}

class Observable {
    private var observersSet: Set<AnyHashable> = []

    private var observers: [Observer] {
        return observersSet.flatMap { $0 as? Observer }
    }

    func add<O>(_ observer: O) where O : Observer, O : Hashable {
        observersSet.insert(observer)
    }

    func remove<O>(_ observer: O) where O : Observer, O : Hashable {
        observersSet.remove(observer)
    }

    // ...

    private func doSomething() {
        // do something ...
        observers.forEach { $0.observableDidSomething(self) }
    }
} 

Notice that I separate the Hashable protocol from my protocol Observer.

GaétanZ
  • 4,870
  • 1
  • 23
  • 30
  • Could someone please explain the syntax of the typing in the add and remove functions? I'm not familiar with the and where O : Observer, O : Hashable syntax. – Mark S Nov 29 '21 at 01:07