Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Created July 27, 2021 21:26
Show Gist options
  • Save IanKeen/f9a9980ab1db8b149a0c70c7597252f6 to your computer and use it in GitHub Desktop.
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
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
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) }
}
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