Протокол, соединяющий NSMutableSet и NSMutableOrderedSet вместе

В Swift 3 я хотел бы иметь возможность создать протокол, который позволяет мне добавлять элементы и выполнять итерации с использованием for element in. Протокол должен работать как с NSMutableSet, так и с NSMutableOrderedSet (поскольку они не наследуются от одного и того же класса).

Я знаю, что есть веские причины, по которым NSMutableSet и NSMutableOrderedSet не наследуются от одного и того же класса, это объясняется здесь и здесь.

Но я хочу создать протокол, который использует только часть всех методов внутри NSMutableSetNSMutableOrderedSet).

Я заставил работать только add, вот так:

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
 */

Однако мне бы очень хотелось иметь возможность перебирать элементы, просто используя:

for element in mutableSet {
    print(element)
}

Я пытаюсь привести protocol MutableSet в соответствие с протоколом Sequence, примерно так, но не получается:

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)
}

Можно ли привести мой протокол в соответствие с Sequence? Как я должен это делать? Я пробовал различные комбинации typealias и associatedtype из Element, Iterator и т. д. Я также пробовал этот ответ, он у меня не работает .

EDIT 2: ответ на мой вопрос в EDIT 1

Я получил var count: Int { get } для работы с этим решением, хотя не уверен, что оно лучшее... Также было бы неплохо не реализовывать var elements: [Any] { get } в расширении NSMutableSet и NSMutableOrderedSet, но я думаю, что это неизбежно?

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: дополнительный вопрос Используя отличный ответ @rob-napier с техникой type erasure, я расширил protocol MutableSet, чтобы иметь способность count, а также subscript, однако я смог сделать это только с помощью func ( по имени getCount), что уродливо, вместо var. Это то, что я использую:

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

Как заставить его работать только с var count: Int { get } и var elements: [Any] в протоколе вместо функций?


person Sajjon    schedule 20.09.2016    source источник
comment
Спасибо! Да, я использовал это, см. мой Edit 2, с таким же решением! Хм... так это решение? Оборачиваем его в закрытие... (подпись метода)   -  person Sajjon    schedule 22.09.2016
comment
Да; это простой способ превратить свойство в функцию. Я не понимаю второй вопрос, хотя об элементах.   -  person Rob Napier    schedule 22.09.2016
comment
Как я могу сделать возможным использование индекса самым простым способом. Я заработал, добавив переменную var elements: [Any] { get } в протокол MutableSet, а затем переменную subscript(position: Int) -> Any. Но для этого требуется дополнительный код в расширении NSMutableSet: extension NSMutableSet: MutableSet { var elements: [Any] { return allObjects } }   -  person Sajjon    schedule 22.09.2016
comment
Я не совсем понимаю, что вы имеете в виду, что этот индекс представляет. Наборы не имеют определенного порядка, поэтому вы не можете превратить набор в коллекцию, проиндексированную в Int. Подписка — это просто синтаксис; это может означать все, что вы хотите. Но если вы имеете в виду второй элемент набора, который не имеет значения для NSSet.   -  person Rob Napier    schedule 22.09.2016
comment
Ты абсолютно прав! Большое спасибо! :) Имеет смысл только для заказанного набора!!   -  person Sajjon    schedule 22.09.2016


Ответы (1)


Ответом почти на каждый вопрос «как мне работать с PAT (протокол с ассоциированным типом)…» будет «положить его в коробку». Это поле представляет собой ластик типа. В вашем случае вы хотите 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)
}

В принципе, есть и другой способ, который состоит в том, чтобы перейти прямо к существующему «протоколу, который позволяет мне добавлять элементы и выполнять итерации с использованием for element in». Это два протокола: SetAlgebra & Sequence. На практике я обнаружил, что приведение NSMutableSet или NSOrderedSet в соответствие с SetAlgebra вызывает... раздражение. NSMutableSet в основном не работает в Swift 3. Он принимает Any в разных местах, но определяется как превышающий AnyHashable. Базовый код не работает:

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

Но это потому, что вы не должны использовать NSMutableSet. Он автоматически подключается к Set, и вместо этого вы должны использовать Set. И Set соответствует SetAlgebra & Sequence, так что все в порядке.

Но затем мы приходим к NSOrderedSet. Это очень сложно соединить со Swift (именно поэтому команда Foundation так долго откладывала это). Это действительно беспорядок типа ИМО, и каждый раз, когда я пытался его использовать, я заканчивал тем, что вытаскивал его, потому что он ни с чем не сочетается. (Попробуйте использовать NSFetchedResultsController, чтобы использовать порядок в «упорядоченных отношениях».) Честно говоря, лучше всего было бы обернуть его в структуру и заставить эту структуру соответствовать SetAlgebra & Sequence.

Но если вы не пойдете по этому пути (или просто избавитесь от упорядоченных наборов, как я всегда в конце концов делаю), то стирание текста будет практически вашим единственным инструментом.

person Rob Napier    schedule 20.09.2016
comment
вы качаетесь, сэр! :D Потрясающе! Большое спасибо! На самом деле я использую это для расширения NSManagedObject для глубокого копирования, аналогично gist.github.com/advantis/ 7642084, но я хочу минимизировать код, зависящий от того, упорядочены ли отношения :) - person Sajjon; 20.09.2016
comment
Я написал дополнительный вопрос, как я могу заставить его работать с возможностью подписки и со свойством count вместо решения func getCount()? Любая помощь высоко ценится! - person Sajjon; 21.09.2016
comment
Я ответил на свой вопрос (в Edit 1) ответом в Edit 2. - person Sajjon; 21.09.2016