За годы работы разработчиком 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

Еще статьи Swift Utilities:

Комментарии (2)


  1. Gargo
    19.11.2023 14:28
    +1

    Поправьте опечатку в названии.

    А по теме - если бы все было так просто, то такой протокол бы уже давно добавила apple. Я вижу как минимум проблемы с быстродействием. И ещё equatable явно создавали для того, чтобы можно было не сравнивать похожие свойства по одному, а убрать их часть из сравнения. Например, полученные из бд объекты и сравненные таким способом всегда будут разные, потому что у них ID отличается


    1. VAnsimov Автор
      19.11.2023 14:28

      Если нужно сравнить отдельные части, то этот протокол не подойдет, если нужно сравнить на полное соответствие, а это большинство случаев, то очень хороший способ