Создание протокола, который представляет хешируемые объекты, которые могут быть включены или выключены

Я пытаюсь создать простой протокол, который говорит, находится ли объект в состоянии "включено" или "выключено". Интерпретация того, что это, зависит от объекта реализации. Для UISwitchэто то, включен ли выключатель (дух). Для UIButton, может быть, кнопка находится в selected Государство или нет. Для Car, может быть, двигатель автомобиля включен или нет, или даже если он движется или нет. Поэтому я решил создать этот простой протокол:

protocol OnOffRepresentable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

Теперь я могу расширить вышеупомянутые элементы управления UI следующим образом:

extension UISwitch: OnOffRepresentable {
    func isInOnState() -> Bool { return on }
    func isInOffState() -> Bool { return !on }
}

extension UIButton: OnOffRepresentable {
    func isInOnState() -> Bool { return selected }
    func isInOffState() -> Bool { return !selected }
}

Теперь я могу создать массив объектов такого типа и обойти его, проверяя, включены они или нет:

let booleanControls: [OnOffRepresentable] = [UISwitch(), UIButton()]
booleanControls.forEach { print($0.isInOnState()) }

Большой! Теперь я хочу сделать словарь, который сопоставляет эти элементы управления с UILabel поэтому я могу изменить текст метки, связанной с элементом управления, когда элемент управления меняет состояние. Итак, я иду, чтобы объявить свой словарь:

var toggleToLabelMapper: [OnOffRepresentable : UILabel] = [:]
// error: type 'OnOffRepresentable' does not conform to protocol 'Hashable'

Ой! Правильно! Дурак я. Итак, позвольте мне просто обновить протокол с использованием композиции протокола (в конце концов, все элементы управления, которые я хочу здесь использовать, являются Hashable: UISwitch, UIButton и т. Д.):

protocol OnOffRepresentable: Hashable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

Но теперь я получаю новый набор ошибок:

error: protocol 'OnOffRepresentable' can only be used as a generic constraint because it has Self or associated type requirements
error: using 'OnOffRepresentable' as a concrete type conforming to protocol 'Hashable' is not supported

Хорошо... Итак, я занимаюсь копанием и поиском переполнения стека. Я нахожу много статей, которые кажутся многообещающими, такие как Set и протоколы в Swift. Использование какого-либо протокола в качестве конкретного типа, соответствующего другому протоколу, не поддерживается, и я вижу, что есть несколько отличных статей о type erasure кажется, это именно то, что мне нужно: http://krakendev.io/blog/generic-protocols-and-their-shortcomings, http://robnapier.net/erasure и https://realm.io/news/type-erased-wrappers-in-swift/ просто назвать несколько.

Это где я застрял, хотя. Я пытался прочитать все это, и я попытался создать класс, который будет Hashable а также соответствуют моим OnOffRepresentable протокол, но я не могу понять, как заставить все это соединиться.

2 ответа

Решение

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

protocol OnOffRepresentable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

extension UISwitch: OnOffRepresentable {
    func isInOnState() -> Bool { return on }
    func isInOffState() -> Bool { return !on }
}

extension UIButton: OnOffRepresentable {
    func isInOnState() -> Bool { return selected }
    func isInOffState() -> Bool { return !selected }
}

struct HashableOnOffRepresentable : OnOffRepresentable, Hashable {

    private let wrapped:OnOffRepresentable
    private let hashClosure:()->Int
    private let equalClosure:Any->Bool

    var hashValue: Int {
        return hashClosure()
    }

    func isInOnState() -> Bool {
        return wrapped.isInOnState()
    }

    func isInOffState() -> Bool {
        return wrapped.isInOffState()
    }

    init<T where T:OnOffRepresentable, T:Hashable>(with:T) {
        wrapped = with
        hashClosure = { return with.hashValue }
        equalClosure = { if let other = $0 as? T { return with == other } else { return false } }
    }
}

func == (left:HashableOnOffRepresentable, right:HashableOnOffRepresentable) -> Bool {
    return left.equalClosure(right.wrapped)
}

func == (left:HashableOnOffRepresentable, right:OnOffRepresentable) -> Bool {
    return left.equalClosure(right)
}

var toggleToLabelMapper: [HashableOnOffRepresentable : UILabel] = [:]

let anySwitch = HashableOnOffRepresentable(with:UISwitch())
let anyButton = HashableOnOffRepresentable(with:UIButton())

var switchLabel:UILabel!
var buttonLabel:UILabel!

toggleToLabelMapper[anySwitch] = switchLabel
toggleToLabelMapper[anyButton] = buttonLabel

Создание протокола с associatedType (или соответствие этому другому протоколу, который имеет associatedType лайк Hashable) сделает этот протокол очень недружелюбным к дженерикам.

Я предложу вам очень простой обходной путь

OnOffRepresentable

Прежде всего нам не нужны 2 функции, которые говорят прямо противоположное право?;)

Так это

protocol OnOffRepresentable {
    func isInOnState() -> Bool
    func isInOffState() -> Bool
}

становится этим

protocol OnOffRepresentable {
    var on: Bool { get }
}

и конечно

extension UISwitch: OnOffRepresentable { }

extension UIButton: OnOffRepresentable {
    var on: Bool { return selected }
}

Сопряжение OnOffRepresentable с UILabel

Теперь мы не можем использовать OnOffRepresentable как Key из Dictionary потому что наш протокол должен быть Hashable, Тогда давайте использовать другую структуру данных!

let elms: [(OnOffRepresentable, UILabel)] = [
    (UISwitch(), UILabel()),
    (UIButton(), UILabel()),
]

Вот и все.

Другие вопросы по тегам