Создание протокола, который представляет хешируемые объекты, которые могут быть включены или выключены
Я пытаюсь создать простой протокол, который говорит, находится ли объект в состоянии "включено" или "выключено". Интерпретация того, что это, зависит от объекта реализации. Для 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()),
]
Вот и все.