2016-09-20 4 views
1

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

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

Но я хочу создать протокол, который использует только часть всех методов внутри 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 и т. Д. Я также пробовал this answer, он не работает для меня.

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: Развейте вопрос Используя отличный ответ на @ грабят-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] в протоколе, вместо функций?

+1

'_getCount = {ms.count}' –

+0

Спасибо! Да, я использовал это, см. Мой Edit 2, с таким решением! Хм ... так это решение тогда? Обертывание его в закрытии ... (подпись метода) – Sajjon

+0

да; это простой способ превратить свойство в функцию. Я не понимаю второй вопрос, хотя об элементах. –

ответ

2

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

В принципе есть еще один способ, который должен идти прямо к существующему «протоколу, который позволяет мне добавлять элементы и перебирать, используя для элемента.» Это два протокола: 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 (именно поэтому команда Фонда отложила это так долго). Это действительно беспорядок типа ИМО, и каждый раз, когда я пытался его использовать, я затягивал его, потому что он не играет ни с чем. (Попробуйте использовать NSFetchedResultsController, чтобы использовать порядок в «упорядоченных отношениях».) Лучше всего было бы заключить его в структуру и сделать эту структуру соответствующей SetAlgebra & Sequence.

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

+0

вы рок сэр! : D Удивительный! Спасибо огромное! Я фактически использую это для расширения на NSManagedObject для глубокой копии, аналогично https://gist.github.com/advantis/7642084, но я хочу свести к минимуму код, зависящий от того, упорядочена ли связь :) – Sajjon

+0

Я написал следующее, как я могу заставить его работать со способностью к индексам и с свойством 'count', а не с решением' func getCount() '? Любая помощь высоко ценится! – Sajjon

+0

Я ответил на свой вопрос (в редакции 1) с ответом в редакторе 2. – Sajjon

Смежные вопросы