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

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

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

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

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-их-недостатки, http://robnapier.net/erasure и https://realm.io/news/type-erased-wrappers-in-swift/ и это лишь некоторые из них.

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


person Tim Fuqua    schedule 19.08.2016    source источник
comment
Сначала я попытался имитировать версию от krakendev, но я зациклился на том факте, что протокол MythicalType был единственным протоколом, которому он пытался соответствовать. Поэтому, когда я пытаюсь заменить MythicalType прямо на мой OnOffRepresentable, этого недостаточно. Не могу понять, как вставить в него часть Hashable. То же самое относится и к блогу Роба Нэпьера, ... может быть. Его Animal протокол является общим для Food ассоциированного типа, но я тоже этим не занимаюсь. Примеры realm.io показались мне лучшими, но SequenceType также сильно отличается от моих конкретных потребностей.   -  person Tim Fuqua    schedule 20.08.2016


Ответы (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
person Daniel Hall    schedule 20.08.2016
comment
Это действительно близко. Я думаю, что с equalClosure что-то не так. Если вы создаете фактические UILabels и сохраняете их в словаре, а затем пытаетесь извлечь только что сохраненную метку, она всегда равна нулю. Это заставляет меня думать, что оператор == работает не так, как задумано. - person Tim Fuqua; 20.08.2016
comment
@TimFuqua. Хороший улов! Я исправил функцию == и добавил дополнительную перегрузку, чтобы вы могли напрямую сравнивать с развернутым экземпляром протокола. Я думаю, это должно решить проблему, но я набираю это на своем телефоне и еще не проверял на игровой площадке - person Daniel Hall; 20.08.2016
comment
Итак, мы идем! Да, я подумал, что это простое решение, но не знал, что именно. Очень хороший ответ. И это хороший, простой подход и пример, на который можно снова ссылаться в будущем. - person Tim Fuqua; 20.08.2016

Создание протокола с 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()),
]

Вот и все.

person Luca Angeletti    schedule 20.08.2016
comment
С самого начала я знал, что уже могу создать массив из OnOffRepresentables. Но дело не в этом. Мне нужно, чтобы они были Hashable для удобного поиска в словаре. Линейный поиск в массиве пар для поиска точного OnOffRepresentable элемента управления не оптимален и плохо масштабируется. Поэтому в примере был упомянут словарь. И мне безразлично изменение 2 функций на 2 вычисляемых свойства (да, 2, потому что я из тех разработчиков, которые предпочитают использовать off вместо !on, поэтому я бы также реализовал свойство off. - person Tim Fuqua; 20.08.2016