· 3 min read
Porównywanie obiektów w Swift za pomocą protokołu Equatable
Jak usprawnić porównywanie obiektów w Swift za pomocą protokołu Equatable? Zobacz na konkretnych przykładach.
Zgodność z protokołem Equatable umożliwia porównywanie obiektów w Swift pod kątem ich równości lub nierówności przy użyciu operatorów (==) i (!=). Dzięki temu możemy na przykład przeprowadzać określone operacje w zależności od tego, czy obiekty są zgodne lub nie.
Equatable jest bardzo łatwy w implementacji, a co lepsze większość podstawowych typów w standardowej bibliotece Swift jest już z nim zgodna. Jednak, w przypadku tworzenia własnych typów, konieczne będzie zaimplementowanie tego protokołu samemu. Podobnie problem możemy napotkać porównując obiekty z zewnętrzych biliotek, które tego protokołu nie zaimplementowały.
Przyjrzyjmy się jak możemy rozwiązać te problemy 👇
Jak zapewnić zgodność z Equatable?
Aby zapewnić typowi porównywalność wystarczy zaimplementować protokół Equatable
, jak poniżej:
struct Dog: Equatable {
let id = UUID()
let name: String
}
Teraz możemy już porównywać obiekty tej struktury dowolnie:
let dog1 = Dog(name: "Doggo")
let dog2 = Dog(name: "Carly")
// Wynik: false
print(dog1 == dog2)
To wszystko, co musimy zrobić dzięki automatycznie syntezowanej zgodności w kompilatorze Swift (ang. “automatically synthesized conformance”).
Dzięki czemu? 🤷♂️
Automatycznie syntezowana zgodność
Wraz ze Swift 4.1 do kompilatora dodano funkcję syntezowania protokołów Equatable
oraz Hashable
, a dokładnie tu: SE-0185. Dzięki temu aby porównać ze sobą dwa obiekty tego samego typu wystarczy, że każdy członek tego typu będzie implementował protokół Equatable
.
Na przykład, struktura poniżej jest zgodna z tym protokołem, ponieważ id (UUID) oraz name (String) są typami, które domyślnie wspierają ten protokół i są z nim zgodne.
struct Dog: Equatable {
let id = UUID()
let name: String
}
Ale weźmy pod uwagę inny przykład 🤔
struct Address {
let city: String
}
struct Person: Equatable {
let id = UUID()
let name: String
let address: Address
}
Tu już mamy problem, bo o ile struktura Person
implementuje protokół Equatable
to Address
już nie, a wtedy z Xcode dostaniemy błąd o treści:
Type ‘Person’ does not conform to protocol ‘Equatable’
Aby ten błąd naprawić, struktura Address
również musi implementować ten protokół.
Bądź na bieżąco z iOS i Swift 📱
Dołącz do newsletter'a i otrzymuj więcej takich treści regularnie na swoją skrzynkę mailową 📬
Zewnętrzne biblioteki
A co jeśli nie mamy bezpośrednio dostępu do kodu struktury czy klasy? Np. jeśli używamy typów dostarczanych przez zewnętrzne biblioteki?
Wtedy z pomocą przychodzą extensiony. Założmy, że chcemy rozszerzyć jakąś strukturę o Equatable
. Wystarczy, że napiszemy:
// To jest jakiś typ dostarczony przez zewnętrzną bibliotekę
extension StructFrom3rdPartyLibrary: Equatable { }
Własna logika porównywania
A co jeśli chcemy porównać ze sobą obiekty w pewien specyficzny sposób?
struct Phone: Equatable {
let id = UUID()
let model: String
let vendor: String
}
Założmy, że chcielibyśmy porównywać ze sobą obiekty telefonów tylko i wyłącznie na podstawie producenta, czyli w tym przypadku pola vendor
.
let phone1 = Phone(model: "Galaxy S10", vendor: "Samsung")
let phone2 = Phone(model: "Galaxy S20", vendor: "Samsung")
Możemy napisać własną implementację funkcji porównującej aby uzyskać porządany efekt:
extension Phone: Equatable {
static func == (lhs: Phone, rhs: Phone) -> Bool {
lhs.vendor == rhs.vendor
}
}
// Wynik: true
print(phone1 == phone2)
A co z enumami?
Spokojnie, enumy są automatycznie syntezowane, więc nie musimy się o nic martwić. No, o ile ich wszystkie wartości i pola są porównywalne, o czym warto pamiętać.
Podsumowanie
Wnioski? Jeśli chcesz porównywać obiekty tego samego typu, zastosuj protokół Equatable
zgodnie z tym, co zaprezentowałem. Możesz opierać się na domyślnej implementacji tego protokołu albo napisać własną logikę porównywania obiektów.