Checking Hashable conformance
The problem with your code is that you're talking in terms of Model
, which promises nothing about Hashable
conformance. As you point out, the problem with telling the compiler about this (i.e deriving Model
from Hashable
) is you then lose the ability to talk in terms of heterogenous types that conform to Model
.
If you don't even care about Model
conformance in the first place, you can just use the standard library's AnyHashable
type-erased wrapper for completely arbitrary Hashable
conforming instances.
However, assuming you do care about Model
conformance, you'll have to build your own type-erased wrapper for instances that conform to both Model
and Hashable
. In my answer here, I demonstrate how a type eraser can be built for Equatable
conforming types. The logic there can be very easily extended for Hashable
– we just need to store an extra function to return the hashValue
of the instance.
For example:
struct AnyHashableModel : Model, Hashable {
static func ==(lhs: AnyHashableModel, rhs: AnyHashableModel) -> Bool {
// forward to both lhs's and rhs's _isEqual in order to determine equality.
// the reason that both must be called is to preserve symmetry for when a
// superclass is being compared with a subclass.
// if you know you're always working with value types, you can omit one of them.
return lhs._isEqual(rhs) || rhs._isEqual(lhs)
}
private let base: Model
private let _isEqual: (_ to: AnyHashableModel) -> Bool
private let _hashValue: () -> Int
init<T : Model>(_ base: T) where T : Hashable {
self.base = base
_isEqual = {
// attempt to cast the passed instance to the concrete type that
// AnyHashableModel was initialised with, returning the result of that
// type's == implementation, or false otherwise.
if let other = $0.base as? T {
return base == other
} else {
return false
}
}
// simply assign a closure that captures base and returns its hashValue
_hashValue = { base.hashValue }
}
var hashValue: Int { return _hashValue() }
}
You would then use it like so:
func complete(with models: [AnyHashableModel]) {
doSomethingWithHashable(models)
}
func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
//
}
let models = [AnyHashableModel(Contact()), AnyHashableModel(Address())]
complete(with: models)
Here I'm assuming that you'll also want to use it as a wrapper for Model
's requirements (assuming there are some). Alternatively, you can expose the base
property and remove the Model
conformance from AnyHashableModel
itself, making callers access the base
for the underlying Model
conforming instance:
struct AnyHashableModel : Hashable {
// ...
let base: Model
// ...
}
You will however note that the above type-erased wrapper is only applicable to types that are both Hashable
and a Model
. What if we want to talk about some other protocol where the conforming instances are Hashable
?
A more general solution, as I demonstrate in this Q&A, is to instead accept types that are both Hashable
and conform to some other protocol – the type of which is expressed by a generic placeholder.
As there's currently no way in Swift to express a generic placeholder that must conform to a protocol given by another generic placeholder; this relationship must be defined by the caller with a transform
closure to perform the necessary upcast. However, thanks to Swift 3.1's acceptance of concrete same-type requirements in extensions, we can define a convenience initialiser to remove this boilerplate for Model
(and this can be repeated for other protocol types).
For example:
/// Type-erased wrapper for a type that conforms to Hashable,
/// but inherits from/conforms to a type T that doesn't necessarily require
/// Hashable conformance. In almost all cases, T should be a protocol type.
struct AnySpecificHashable<T> : Hashable {
static func ==(lhs: AnySpecificHashable, rhs: AnySpecificHashable) -> Bool {
return lhs._isEqual(rhs) || rhs._isEqual(lhs)
}
let base: T
private let _isEqual: (_ to: AnySpecificHashable) -> Bool
private let _hashValue: () -> Int
init<U : Hashable>(_ base: U, upcast: (U) -> T) {
self.base = upcast(base)
_isEqual = {
if let other = $0.base as? U {
return base == other
} else {
return false
}
}
_hashValue = { base.hashValue }
}
var hashValue: Int { return _hashValue() }
}
// extension for convenience initialiser for when T is Model.
extension AnySpecificHashable where T == Model {
init<U : Model>(_ base: U) where U : Hashable {
self.init(base, upcast: { $0 })
}
}
You would now want to wrap your instances in a AnySpecificHashable<Model>
:
func complete(with models: [AnySpecificHashable<Model>]) {
doSomethingWithHashable(models)
}
func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
//
}
let models: [AnySpecificHashable<Model>] = [
AnySpecificHashable(Contact()),
AnySpecificHashable(Address())
]
complete(with: models)
Check if Any conforms to Hashable and get hash value
You need to use AnyHashable
instead of Hashable
, which is the type erased version of the Hashable
protocol created to resolve that specific error you are encountering.
if let h = anyValue as? AnyHashable {
Conforming to Hashable protocol?
You're missing the declaration:
struct DateStruct: Hashable {
And your ==
function is wrong. You should compare the three properties.
static func == (lhs: DateStruct, rhs: DateStruct) -> Bool {
return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day
}
It's possible for two different values to have the same hash value.
Conditional Conformance to Hashable
The conditional compilation statement #if swift(...)
checks against the language version you're running with – which can differ from the compiler (and therefore standard library) version.
In your case, it sounds like you're using a Swift 4.2 compiler in Swift 4 compatibility mode, which gives you a language version of 4.1.50. This therefore passes the conditional compilation statement and your extension is compiled, giving you a duplicate conformance.
In order to check for a compiler version less than 4.2, you want the following:
// less than 4.2 in Swift 4 compat mode and (less than 4.2 in 3 compat mode or
// greater than 4).
#if !swift(>=4.1.50) && (!swift(>=3.4) || swift(>=4.0))
extension Array : Hashable where Element : Hashable {
public var hashValue: Int {
let prime = 31
var result = 1
for element in self {
result = prime * result + element.hashValue
}
return result
}
}
#endif
Things will be much better with the new #if compiler
directive, which is available in Swift 4.2 from Xcode 10 beta 4 onwards (confirmed by @MartinR). With that, you can directly test for the compiler version, ignoring any compatibility modes it might be running in.
This doesn't help your specific case as you need the code to be understood by both Swift 4.2 and 4.1 compilers (as also pointed out by Martin), but for future compatibility problems, you could for example use #if !compiler(>=5)
in order to only compile a block of code if using a 4.2 compiler.
Make a swift protocol conform to Hashable
Deriving the protocol from Hashable
and using a type eraser might help here:
protocol SomeLocation: Hashable {
var name: String { get }
var coordinates: Coordinate { get }
}
struct AnyLocation: SomeLocation {
let name: String
let coordinates: Coordinate
init<L: SomeLocation>(_ location: L) {
name = location.name
coordinates = location.coordinates
}
}
You then can simply declare the protocol conformance on the structs, and if Coordinate
is already Hashable
, then you don't need to write any extra hashing code code, since the compiler can automatically synthesize for you (and so will do for new types as long as all their properties are Hashable
:
struct ShopLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
struct CarLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}
If Coordinate
is also Codable
, then you also can omit writing any code for the encoding/decoding operations, the compile will synthesize the required methods (provided all other properties are already Codable
).
You can then use the eraser within the annotation class by forwardingn the initializer constraints:
final class LocationAnnotation: NSObject, MKAnnotation {
let location: AnyLocation
init<L: SomeLocation>(location: L) {
self.location = AnyLocation(location)
super.init()
}
override var hash: Int {
location.hashValue
}
override func isEqual(_ object: Any?) -> Bool {
(object as? LocationAnnotation)?.location == location
}
}
Swift, how to implement Hashable protocol based on object reference?
If you are working with classes and not structs, you can use the ObjectIdentifier
struct. Note that you also have to define ==
for your class in order to conform to Equatable
(Hashable
requires it). It would look something like this:
class MyClass: Hashable { }
func ==(lhs: MyClass, rhs: MyClass) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
class MyClass: Hashable {
var hashValue: Int {
return ObjectIdentifier(self).hashValue
}
}
How to make an enum conform to Hashable with the API available in Xcode 10?
You can use autogenerated Hashable
conformance, as proposed in the other answer (under condition your type doesn't contains any date of non-Hashable
types).
But that's what you can do in the general case(autogenerated code would probably look like that too):
extension MyEnum: Hashable {
func hash(into hasher: inout Hasher) {
switch self {
case .caseOne(let value):
hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
case .caseTwo(let value):
hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
case .caseThree:
// you can `combine` with some `Hashable` constant, but here it's ok just to skip
break
}
}
}
Creating a protocol that represents hashable objects that can be on or off
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
Related Topics
"Message from Debugger: Unable to Attach" When Running Tests on Osx App
Swiftui on MACos - Handle Single-Click and Double-Click at the Same Time
Initializing Property via Closure
Swiftui - Show the Data Fetched from Firebase in View
Swift 'Unable to Dequeue a Cell with Identifier Intervalcellidentifier
Implementing Stringliteralconvertible on Nsurl
How to Pass/Get Core Data Context in Swiftui Mvvm Viewmodel
Cursor Shifts to End on Edit of Formatted Decimal Textfield - Swift
Swift - Kvo - Change.Newvalue and Change.Oldvalue Are Nil
Swiftui Behavior of .Frame(Height: Nil)
Rotate a Sprite to Sprite Position Not Exact in Spritekit with Swift
Swiftui - Make Toolbar's Navigationlink Use Detail View
Displaying State of an Async API Call in Swiftui
Uisegment Value Changing When Tableview Get Scrolled