Swift Utilities — Equatable для сложных Enum
За годы работы разработчиком iOS, я собрал множество инструментов и полезных штук, которые облегчают процесс разработки. В этой статье, я хочу поделиться одним из таких инструментов. Это будет не большая статья. Я покажу, как пользоваться этой утилитой, продемонстрирую её в действии. Надеюсь, что статья окажется полезной для вас.
Мне часто встречалась ситуация когда надо подписать enum со вложенными типами под протокол Equatable и приходилось реализовывать его функции
static func ==(lhs: T, rhs: T) -> Bool
Чтобы упростить жизнь и каждый раз не писать сложные static func ==(lhs: T, rhs: T) -> Bool
Можно подписать enum под protocol ComplexEquatable
/// ```swift
/// enum SomeState: ComplexEquatable {
/// case text(SomeText)
/// }
///
/// class SomeText {
/// var text: String?
///
/// init(text: String?) {
/// self.text = text
/// }
/// }
///
/// let objectOne = SomeState.text(.init(text: nil))
/// let objectTwo = SomeState.text(.init(text: "Hello, World!"))
/// objectOne == objectTwo // false
///
/// let objectOne = SomeState.text(.init(text: "Hello, World!"))
/// let objectTwo = SomeState.text(.init(text: "Hello, World!"))
/// objectOne == objectTwo // true
///
/// let objectOne = SomeState.text(.init(text: "Hello"))
/// let objectTwo = SomeState.text(.init(text: "Hello, World!"))
/// objectOne == objectTwo // false
/// ```
public protocol ComplexEquatable: Hashable, Equatable {}
extension ComplexEquatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.hashValue == rhs.hashValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(getObjectInfo(of: self))
}
private func getObjectInfo(of instance: Any) -> String {
let mirror = Mirror(reflecting: instance)
var result = ""
for (index, child) in mirror.children.enumerated() {
if let propertyName = child.label {
result += "\(index == 0 ? "" : ",")\(propertyName):\(child.value)"
let childString = getObjectInfo(of: child.value)
if !childString.isEmpty {
result += "{\(childString)}"
}
}
}
if result == "" {
return String(describing: instance)
}
return result
}
}
Сердцем ComplexEquatable является метод getObjectInfo, использующий Mirror для рефлексии объектов. Этот метод позволяет исследовать структуру объекта и генерировать уникальное хэш-значение, основываясь на всех его свойствах. Это ключ к тому, чтобы два объекта, идентичные по структуре и содержанию, считались одинаковыми.
Пример использования
Рассмотрим несколько примеров для enum.
enum SomeState: ComplexEquatable {
case text(SomeText)
}
class SomeText {
var text: String?
init(text: String?) {
self.text = text
}
}
Когда мы сравниваем два объекта SomeState, содержащих nil и строку "Hello, World!" соответственно, результатом будет false, так как их содержимое различно.
let objectOne = SomeState.text(.init(text: nil))
let objectTwo = SomeState.text(.init(text: "Hello, World!"))
objectOne == objectTwo // false
Однако, если сравнивать два объекта с одинаковыми строками "Hello, World!", результатом будет true. Это демонстрирует гибкость ComplexEquatable в различных сценариях сравнения.
let objectOne = SomeState.text(.init(text: "Hello, World!"))
let objectTwo = SomeState.text(.init(text: "Hello, World!"))
objectOne == objectTwo // true