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?