Protocol bridging NSMutableSet and NSMutableOrderedSet together

旧街凉风 提交于 2019-12-11 05:17:35

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/39593295/protocol-bridging-nsmutableset-and-nsmutableorderedset-together

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!