Created
July 27, 2021 21:26
-
-
Save IanKeen/f9a9980ab1db8b149a0c70c7597252f6 to your computer and use it in GitHub Desktop.
PropertyWrapper: Custom/Selective Equatable & Hashable conformance using PWs to choose which properties are used
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct Model: CustomHashable { | |
@Hashing var name: String | |
var unhashable: () -> Void | |
} | |
let a = Model(name: "Ian", unhashable: { }) | |
let b = Model(name: "Ian", unhashable: { }) | |
a == b // true | |
let c = Model(name: "Jane", unhashable: { }) | |
a == c // false | |
b == c // false | |
let items = Set([a, b, c]) | |
items.count // 2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public protocol CustomEquatable: Equatable { } | |
private extension CustomEquatable { | |
var equatables: [AnyEquatable] { | |
return Mirror(reflecting: self) | |
.children | |
.compactMap { ($0.value as? EquatingProperty)?.anyEquatable } | |
} | |
} | |
extension CustomEquatable { | |
public static func ==(lhs: Self, rhs: Self) -> Bool { | |
let lhsValues = lhs.equatables | |
assert(!lhsValues.isEmpty, "No @Hashed/@Equating properties were found on the left operand") | |
let rhsValues = rhs.equatables | |
assert(!rhsValues.isEmpty, "No @Hashed/@Equating properties were found on the right operand") | |
return lhsValues == rhsValues | |
} | |
} | |
public struct AnyEquatable: Equatable { | |
public let base: Any | |
private let isEqual: (_ other: Any) -> Bool | |
public init<T: Equatable>(_ value: T) { | |
self.base = value | |
self.isEqual = { other in | |
guard let other = other as? T else { return false } | |
return other == value | |
} | |
} | |
public static func ==(lhs: AnyEquatable, rhs: AnyEquatable) -> Bool { | |
return lhs.isEqual(rhs.base) | |
} | |
} | |
private protocol EquatingProperty { | |
var anyEquatable: AnyEquatable { get } | |
} | |
@propertyWrapper | |
public struct Equating<T: Equatable>: EquatingProperty { | |
public var wrappedValue: T | |
public init(wrappedValue: T) { self.wrappedValue = wrappedValue } | |
var anyEquatable: AnyEquatable { .init(wrappedValue) } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public protocol CustomHashable: CustomEquatable, Hashable { } | |
extension CustomHashable { | |
public func hash(into hasher: inout Hasher) { | |
let values = Mirror(reflecting: self) | |
.children | |
.compactMap { ($0.value as? HashingProperty) } | |
assert(!values.isEmpty, "No @Hashed properties were found") | |
for value in values { | |
value.hash(into: &hasher) | |
} | |
} | |
} | |
private protocol HashingProperty { | |
func hash(into hasher: inout Hasher) | |
} | |
@propertyWrapper | |
public struct Hashing<T: Hashable>: EquatingProperty, HashingProperty { | |
public var wrappedValue: T | |
var anyEquatable: AnyEquatable { .init(wrappedValue) } | |
public init(wrappedValue: T) { self.wrappedValue = wrappedValue } | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(wrappedValue) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment