HashableByKeyPath helps avoid common mistakes when implementing Hashable and Equatable conformance:
- Comparing the properties of the same object, e.g.
lhs.foo == lhs.foo - Comparing the wrong properties, e.g.
lhs.foo1 == rhs.foo2 - Checking different properties in
==andhash(into:)functions; "Two instances that are equal must feed the same values toHasherinhash(into:), in the same order"
structFoo:HashableKeyPathProvider{@HashableKeyPathCollectionBuilder<TestObject>staticvarhashableKeyPaths:HashableKeyPathCollection<Foo>{
\Foo.bar1
\Foo.bar2
\Foo.bar3
}varbar1:Stringvarbar2:Stringvarbar3:Int}letfoo1=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo2=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo3=Foo(bar:"value2", bar2:"value2", bar3:"value3")letfoos:Set=[foo1, foo2]
foo1 == foo1 // true
foo1 == foo2 // true
foo1 == foo3 // false
foo2 == foo3 // false
foos.count // 1
foos.contains(foo2) // true
foos.contains(foo3) // falseIf the type only needs to conform to Equatable the type can conform to EquatableByKeyPath:
structFoo:EquatableKeyPathProvider{@EquatableKeyPathCollectionBuilder<TestObject>staticvarequatableKeyPaths:EquatableKeyPathCollection<Foo>{
\Foo.bar1
\Foo.bar2
\Foo.bar3
}varbar1:Stringvarbar2:Stringvarbar3:Int}letfoo1=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo2=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo3=Foo(bar:"value2", bar2:"value2", bar3:"value3")
foo1 == foo1 // true
foo1 == foo2 // true
foo1 == foo3 // false
foo2 == foo3 // falseWhen using a non-final class the HashableKeyPathProvider and EquatableKeyPathProvider protocols cannot be used. In this case the HashableByKeyPath and EquatableByKeyPath protocols should be used:
structFoo:HashableByKeyPath{staticfunc addHashableKeyPaths<Consumer:HashableKeyPathConsumer>(to consumer:inoutConsumer)where Consumer.Root ==Self{
consumer.addHashableKeyPath(\.bar1)
consumer.addHashableKeyPath(\.bar2)
consumer.addHashableKeyPath(\.bar3)}varbar1:Stringvarbar2:Stringvarbar3:Int}letfoo1=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo2=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo3=Foo(bar:"value2", bar2:"value2", bar3:"value3")letfoos:Set=[foo1, foo2]
foo1 == foo1 // true
foo1 == foo2 // true
foo1 == foo3 // false
foo2 == foo3 // false
foos.count // 1
foos.contains(foo2) // true
foos.contains(foo3) // falseIf the type only needs to conform to Equatable the type can conform to EquatableByKeyPath:
structFoo:EquatableByKeyPath{staticfunc addHashableKeyPaths<Consumer:HashableKeyPathConsumer>(to consumer:inoutConsumer)where Consumer.Root ==Self{
consumer.addEquatableKeyPath(\.bar1)
consumer.addEquatableKeyPath(\.bar2)
consumer.addEquatableKeyPath(\.bar3)}varbar1:Stringvarbar2:Stringvarbar3:Int}letfoo1=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo2=Foo(bar:"value", bar2:"value2", bar3:"value3")letfoo3=Foo(bar:"value2", bar2:"value2", bar3:"value3")
foo1 == foo1 // true
foo1 == foo2 // true
foo1 == foo3 // false
foo2 == foo3 // falseHashableByKeyPath supports installation via SwiftPM. This can be done by adding the package to the dependencies section and as the dependency of a target:
letpackage=Package(...
dependencies:[.package(url:"https://github.com/JosephDuffy/HashableByKeyPath.git", from:"1.0.0"),],
targets:[.target(name:"MyApp", dependencies:["HashableByKeyPath"]),],...)HashableByKeyPath is fully documented, with code-level documentation available online. The online documentation is generated from the source code with every release, so it is up-to-date with the latest release, but may be different to the code in master.
HashableByKeyPath has a full test suite, which is run on GitHub actions as part of pull requests. All tests must pass for a pull request to be merged.
Code coverage is collected and reported to to Codecov.
The project is released under the MIT license. View the LICENSE file for the full license.