Sajjon Sajjon - 2 months ago 17
Swift Question

Protocol bridging NSMutableSet and NSMutableOrderedSet together

In Swift 3, I would like to be able to create a protocol which allows me to add elements and iterate through using

for element in
. The protocol should works on both
NSMutableSet
and
NSMutableOrderedSet
(since they do not inherit from the same class).

I know there are good reasons why
NSMutableSet
and
NSMutableOrderedSet
do not inherit from the same class, it is explained here and here.

But I want to create a protocol which only makes use of a fraction of all the methods inside
NSMutableSet
(and
NSMutableOrderedSet
).

I have gotten just the
add
to work, like this:

protocol MutableSet {
func add(_ element: Any)
}

extension NSMutableSet: MutableSet {}
extension NSMutableOrderedSet: MutableSet {}

let one: NSString = "one"
let two: NSString = "two"

// Works if created with `NSMutableSet`
let mutableSet: MutableSet = NSMutableSet()

mutableSet.add(one)
mutableSet.add(two)

for element in mutableSet as! NSMutableSet {
print(element)
}
/*
This prints:
one
two
*/

// Also works if creating `NSMutableOrderedSet` instance
let mutableOrderedSet: MutableSet = NSMutableOrderedSet()
mutableOrderedSet.add(one)
mutableOrderedSet.add(two)
for element in mutableOrderedSet as! NSMutableOrderedSet {
print(element)
}
/*
This prints:
one
two
*/


However I would really love to be able to iterate through the elements just by using:

for element in mutableSet {
print(element)
}


I am trying to make
protocol MutableSet
conform to the
Sequence
protocol, something like this, but it does not work:

protocol MutableSet: Sequence {
func add(_ element: Any)
}

extension NSMutableSet: MutableSet {
typealias Iterator = NSFastEnumerationIterator
typealias Element = NSObject // I dont know what to write here
typealias SubSequence = Slice<Set<NSObject>> // Neither here....
}

let one: NSString = "one"
let two: NSString = "two"

let mutableSet: MutableSet = NSMutableSet() // Compile Error: Protocol `MutableSet` can only be used as a generic constraint because it has Self or associated type requirements
mutableSet.add(one)
mutableSet.add(two)

for element in mutableSet { // Compile Error: Using `MutableSet` as a concrete type conforming to protocol `Sequence` is not supported
print(element)
}


Is it possible to make my protocol conform to
Sequence
? How should I do it? I have tried various combinations of
typealias
and
associatedtype
of
Element
,
Iterator
etc. I also tried this answer it does not work for me.

Thanks!

Answer

The answer to almost every "how do I with a PAT (protocol with associated type)..." is "put it in a box." That box is a type eraser. In your case you want an AnyMutableSet.

import Foundation

// Start with your protocol
protocol MutableSet: Sequence {
    func add(_ element: Any)
}

// Now say that NSMutableSet is one. There is no step two here. Everything can be inferred.
extension NSMutableSet: MutableSet {}

// Create a type eraser for MutableSet. Note that I've gone ahead and made it generic.
// You could lock it down to just Any, but why limit yourself
struct AnyMutableSet<Element>: MutableSet {
    private let _add: (Any) -> ()
    func add(_ element: Any) { _add(element) }
    private let _makeIterator: () -> AnyIterator<Element>
    func makeIterator() -> AnyIterator<Element> { return _makeIterator() }
    init<MS: MutableSet>(_ ms: MS) where MS.Iterator.Element == Element {
        _add = ms.add
        _makeIterator = { AnyIterator(ms.makeIterator()) }
    }
}

// Now we can use it
let one: NSString = "one"
let two: NSString = "two"

// Wrap it in an AnyMutableSet
let mutableSet = AnyMutableSet(NSMutableSet())
mutableSet.add(one)
mutableSet.add(two)

for element in mutableSet {
    print(element)
}

In principle there is another way, which is to go straight to the existing " protocol which allows me to add elements and iterate through using for element in." That's two protocols: SetAlgebra & Sequence. In practice, I've found getting either NSMutableSet or NSOrderedSet to conform to SetAlgebra to be....annoying. NSMutableSet is basically broken in Swift 3. It accepts Any in various places, but is defined as being over AnyHashable. Basic code doesn't work:

let s = NSMutableSet()
let t = NSMutableSet()
s.union(t)

But that's because you're not supposed to use NSMutableSet. It's bridged automatically to Set, and you're supposed to use Set instead. And Set does conform to SetAlgebra & Sequence so that would be fine.

But then we come to NSOrderedSet. This is very hard to bridge into Swift (which is why the Foundation team has deferred it so long). It's really a mess of a type IMO, and every time I've tried to use it, I wound up pulling it out because it doesn't play nicely with anything. (Try to use an NSFetchedResultsController to make use of the order in an "ordered relationship.") Your best bet frankly would be to wrap it up in a struct and make that struct conform to SetAlgebra & Sequence.

But if you don't go that way (or just get rid of the ordered sets, like I always eventually do), then type erasure is pretty much your only tool.