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 toHasher
inhash(into:)
, in the same order"
struct Foo: HashableKeyPathProvider {
@HashableKeyPathCollectionBuilder<TestObject>
static var hashableKeyPaths: HashableKeyPathCollection<Foo> {
\Foo.bar1
\Foo.bar2
\Foo.bar3
}
var bar1: String
var bar2: String
var bar3: Int
}
let foo1 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo2 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo3 = Foo(bar: "value2", bar2: "value2", bar3: "value3")
let foos: 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) // false
If the type only needs to conform to Equatable
the type can conform to EquatableByKeyPath
:
struct Foo: EquatableKeyPathProvider {
@EquatableKeyPathCollectionBuilder<TestObject>
static var equatableKeyPaths: EquatableKeyPathCollection<Foo> {
\Foo.bar1
\Foo.bar2
\Foo.bar3
}
var bar1: String
var bar2: String
var bar3: Int
}
let foo1 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo2 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo3 = Foo(bar: "value2", bar2: "value2", bar3: "value3")
foo1 == foo1 // true
foo1 == foo2 // true
foo1 == foo3 // false
foo2 == foo3 // false
When using a non-final class the HashableKeyPathProvider
and EquatableKeyPathProvider
protocols cannot be used. In this case the HashableByKeyPath
and EquatableByKeyPath
protocols should be used:
struct Foo: HashableByKeyPath {
static func addHashableKeyPaths<Consumer: HashableKeyPathConsumer>(to consumer: inout Consumer) where Consumer.Root == Self {
consumer.addHashableKeyPath(\.bar1)
consumer.addHashableKeyPath(\.bar2)
consumer.addHashableKeyPath(\.bar3)
}
var bar1: String
var bar2: String
var bar3: Int
}
let foo1 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo2 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo3 = Foo(bar: "value2", bar2: "value2", bar3: "value3")
let foos: 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) // false
If the type only needs to conform to Equatable
the type can conform to EquatableByKeyPath
:
struct Foo: EquatableByKeyPath {
static func addHashableKeyPaths<Consumer: HashableKeyPathConsumer>(to consumer: inout Consumer) where Consumer.Root == Self {
consumer.addEquatableKeyPath(\.bar1)
consumer.addEquatableKeyPath(\.bar2)
consumer.addEquatableKeyPath(\.bar3)
}
var bar1: String
var bar2: String
var bar3: Int
}
let foo1 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo2 = Foo(bar: "value", bar2: "value2", bar3: "value3")
let foo3 = Foo(bar: "value2", bar2: "value2", bar3: "value3")
foo1 == foo1 // true
foo1 == foo2 // true
foo1 == foo3 // false
foo2 == foo3 // false
HashableByKeyPath
supports installation via SwiftPM. This can be done by adding the package to the dependencies section and as the dependency of a target:
let package = 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.