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.
EDIT 2: Answer to my own question in EDIT 1
I got var count: Int { get }
to work using this solution, not sure if it is the best one though... Also would be nice to not having to implement the var elements: [Any] { get }
in the extension of NSMutableSet
and NSMutableOrderedSet
, but I guess that is inevitable?
protocol MutableSet: Sequence {
subscript(position: Int) -> Any { get }
func add(_ element: Any)
var count: Int { get }
var elements: [Any] { get }
}
extension MutableSet {
subscript(position: Int) -> Any {
return elements[position]
}
}
extension NSMutableSet: MutableSet {
var elements: [Any] {
return allObjects
}
}
extension NSMutableOrderedSet: MutableSet {
var elements: [Any] {
return array
}
}
struct AnyMutableSet<Element>: MutableSet {
private let _add: (Any) -> ()
private let _makeIterator: () -> AnyIterator<Element>
private var _getElements: () -> [Any]
private var _getCount: () -> Int
func add(_ element: Any) { _add(element) }
func makeIterator() -> AnyIterator<Element> { return _makeIterator() }
var count: Int { return _getCount() }
var elements: [Any] { return _getElements() }
init<MS: MutableSet>(_ ms: MS) where MS.Iterator.Element == Element {
_add = ms.add
_makeIterator = { AnyIterator(ms.makeIterator()) }
_getElements = { ms.elements }
_getCount = { ms.count }
}
}
let one: NSString = "one"
let two: NSString = "two"
let mutableSet: AnyMutableSet<Any>
let someCondition = true
if someCondition {
mutableSet = AnyMutableSet(NSMutableSet())
} else {
mutableSet = AnyMutableSet(NSMutableOrderedSet())
}
mutableSet.add(one)
mutableSet.add(two)
for i in 0..<mutableSet.count {
print("Element[\(i)] == \(mutableSet[i])")
}
// Prints:
// Element[0] == one
// Element[1] == two
EDIT 1: Follow up question
Using the excellent answer by @rob-napier with type erasure
technique I have extended the protocol MutableSet
to have the count
and also subscript
ability, however I was only able to do so using func
(named getCount
) which is ugly, instead of var
. This is what I am using:
protocol MutableSet: Sequence {
subscript(position: Int) -> Any { get }
func getCount() -> Int
func add(_ element: Any)
func getElements() -> [Any]
}
extension MutableSet {
subscript(position: Int) -> Any {
return getElements()[position]
}
}
extension NSMutableSet: MutableSet {
func getCount() -> Int {
return count
}
func getElements() -> [Any] {
return allObjects
}
}
extension NSMutableOrderedSet: MutableSet {
func getElements() -> [Any] {
return array
}
func getCount() -> Int {
return count
}
}
struct AnyMutableSet<Element>: MutableSet {
private var _getCount: () -> Int
private var _getElements: () -> [Any]
private let _add: (Any) -> ()
private let _makeIterator: () -> AnyIterator<Element>
func getElements() -> [Any] { return _getElements() }
func add(_ element: Any) { _add(element) }
func makeIterator() -> AnyIterator<Element> { return _makeIterator() }
func getCount() -> Int { return _getCount() }
init<MS: MutableSet>(_ ms: MS) where MS.Iterator.Element == Element {
_add = ms.add
_makeIterator = { AnyIterator(ms.makeIterator()) }
_getElements = ms.getElements
_getCount = ms.getCount
}
}
let one: NSString = "one"
let two: NSString = "two"
let mutableSet: AnyMutableSet<Any>
let someCondition = true
if someCondition {
mutableSet = AnyMutableSet(NSMutableSet())
} else {
mutableSet = AnyMutableSet(NSMutableOrderedSet())
}
mutableSet.add(one)
mutableSet.add(two)
for i in 0..<mutableSet.getCount() {
print("Element[\(i)] == \(mutableSet[i])")
}
// Prints:
// Element[0] == one
// Element[1] == two
How can I get it to work with just var count: Int { get }
and var elements: [Any]
in the protocol, instead of functions?
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.