I'm trying to create a simple protocol that says whether or not an object is in an "on" state or an "off" state. The interpretation of what that is depends on the implementing object. For a UISwitch
, it's whether the switch is on or off (duh). For a UIButton
, it could be whether the button is in the selected
state or not. For a Car
, it could be whether the car's engine is on or not, or even if it is moving or not. So I set out to create this simple protocol:
protocol OnOffRepresentable {
func isInOnState() -> Bool
func isInOffState() -> Bool
}
Now I can extent the aforementioned UI controls like so:
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 }
}
Now I can make an array of these kinds of objects and loop over it checking whether they are on or off:
let booleanControls: [OnOffRepresentable] = [UISwitch(), UIButton()]
booleanControls.forEach { print($0.isInOnState()) }
Great! Now I want to make a dictionary that maps these controls to a UILabel
so I can change the text of the label associated with the control when the control changes state. So I go to declare my dictionary:
var toggleToLabelMapper: [OnOffRepresentable : UILabel] = [:]
// error: type 'OnOffRepresentable' does not conform to protocol 'Hashable'
Oh! Right! Silly me. Ok, so let me just update the protocol using protocol composition (after all, the controls I want to use here are all Hashable: UISwitch, UIButton, etc):
protocol OnOffRepresentable: Hashable {
func isInOnState() -> Bool
func isInOffState() -> Bool
}
But now I get a new set of errors:
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
Ok... So I do some stack overflow digging and searching. I find many articles that seem promising, like Set and protocols in Swift, Using some protocol as a concrete type conforming to another protocol is not supported, and I see that there are some great articles out there on type erasure
that seem to be exactly what I need: http://krakendev.io/blog/generic-protocols-and-their-shortcomings, http://robnapier.net/erasure, and https://realm.io/news/type-erased-wrappers-in-swift/ just to name a few.
This is where I get stuck though. I've tried reading through all these, and I've tried to create a class that will be Hashable
and also conform to my OnOffRepresentable
protocol, but I can't figure out how to make it all connect.
In Swift, a Hashable is a protocol that provides a hashValue to our object. The hashValue is used to compare two instances. To use the hashValue , we first have to conform (associate) the type (struct, class, etc) to Hashable property. For example, struct Employee: Hashable { ... }
To add Hashable conformance, provide an == operator function and implement the hash(into:) method. The hash(into:) method in this example feeds the grid point's x and y properties into the provided hasher.
In Swift, a protocol defines a blueprint of methods or properties that can then be adopted by classes (or any other types). We use the protocol keyword to define a protocol. For example, protocol Greet { // blueprint of a property var name: String { get } // blueprint of a method func message() }
Inspection of the NSObject class reveals that it (or the NSObjectProtocol that it maps to) does not implement the hashValue method required by the Hashable protocol, nor does it explicity adopt it.
I don't know if I'd necessarily make the OnOffRepresentable
protocol inherit from Hashable
. It doesn't seem like something that you'd want to be represented as on or off must also be hashable. So in my implementation below, I add the Hashable
conformance to the type erasing wrapper only. That way, you can reference OnOffRepresentable
items directly whenever possible (without the "can only be used in a generic constraint" warning), and only wrap them inside the HashableOnOffRepresentable
type eraser when you need to place them in sets or use them as dictionary keys.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With