Как правильно реализовать соответствие протоколу Equatable для структуры в Swift?

«Как правильно реализовать соответствие протоколу Equatable для структуры в Swift?» — вопрос из категории Swift Core, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Автоматическая реализация (Swift 4.1+) Если все свойства структуры уже соответствуют Equatable, компилятор сгенерирует реализацию автоматически:

struct Point: Equatable {
    var x: Double  // Double уже Equatable
    var y: Double  // Double уже Equatable
    // Компилятор автоматически генерирует ==
}

let p1 = Point(x: 1.0, y: 2.0)
let p2 = Point(x: 1.0, y: 2.0)
print(p1 == p2) // true

Ручная реализация Требуется, когда:

  1. Есть свойства, не соответствующие Equatable
  2. Нужна кастомная логика сравнения
  3. Работаете с версией Swift до 4.1
struct Person: Equatable {
    var name: String
    var age: Int
    var address: Address  // Кастомный тип

    // Ручная реализация ==
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && 
               lhs.age == rhs.age && 
               lhs.address == rhs.address  // Address должен быть Equatable
    }
}

// Вспомогательная структура
struct Address: Equatable {
    var street: String
    var city: String
    // Автоматическая реализация, так как все свойства Equatable
}

Особые случаи:

1. Enum с ассоциированными значениями

enum Result: Equatable {
    case success(value: Int)      // Int уже Equatable
    case failure(error: String)   // String уже Equatable
    // Автоматическая реализация доступна
}

2. Игнорирование некоторых свойств

struct User: Equatable {
    var id: UUID
    var name: String
    var lastLogin: Date  // Игнорируем при сравнении

    static func == (lhs: User, rhs: User) -> Bool {
        // Сравниваем только id и name
        return lhs.id == rhs.id && lhs.name == rhs.name
    }
}

3. Рекурсивные структуры

class Node {
    var value: Int
    var children: [Node] = []

    init(value: Int) {
        self.value = value
    }
}

// Для сравнения по значению, а не по ссылке
extension Node: Equatable {
    static func == (lhs: Node, rhs: Node) -> Bool {
        return lhs.value == rhs.value && 
               lhs.children == rhs.children  // Рекурсивное сравнение
    }
}

Best Practices:

  • Для enum с ассоциированными значениями все типы должны быть Equatable
  • Реализуйте == симметричным: a == b должно давать тот же результат, что и b == a
  • Для Hashable сначала реализуйте Equatable
  • Избегайте сравнения floating-point чисел на точное равенство