Protokoły z associatedtype i ograniczeniami where to potężny mechanizm abstrakcji typów w Swift. Często są stosowane do implementacji ogólnych protokołów, gdzie konkretny typ określany jest przez implementację. Historycznie, w pierwszych wersjach Swift protokoły z associatedtype nie mogły być używane jako konkretne typy (typy egzemplarzowe), co narzucało ograniczenia na zastosowanie takich protokołów w kolekcjach i interfejsach. Później Swift dodał mechanizmy do ograniczenia associatedtype za pomocą warunków where, co pozwala na tworzenie elastycznych i bezpiecznych abstrakcji dla złożonych scenariuszy.
Problem: często pojawia się potrzeba stworzenia protokołu, który pozwala na abstrahowanie od konkretnych kolekcji lub przetworników danych. Jednak standardowe protokoły mogą być niewystarczająco elastyczne: nie wszystkie typy można uogólnić bez znajomości powiązanych typów, a logika dziedziczenia protokołów z associatedtype nie zawsze jest oczywista.
Rozwiązanie: użyć ograniczeń where, aby doprecyzować wymagania dla implementacji, co pozwala na tworzenie protokołów o adaptacyjnym zachowaniu.
Przykład kodu:
protocol Storage { associatedtype Element func add(_ item: Element) } // Ograniczony protokół do przechowywania tylko liczb protocol NumericStorage: Storage where Element: Numeric { func sum() -> Element } struct IntStorage: NumericStorage { private var items: [Int] = [] func add(_ item: Int) { items.append(item) } func sum() -> Int { items.reduce(0, +) } }
Kluczowe cechy:
Czy można użyć protokołu z associatedtype jako typ (na przykład, do kolekcji)?
Nie, nie można bezpośrednio. Protokoł z associatedtype to PAT (protocol with associated type) i nie może być używany jako typ egzemplarzowy. Nie da się na przykład zadeklarować tablicy [Storage], można to zrobić tylko za pomocą erozji typu.
Jak zaimplementować erozję typu dla protokołu z associatedtype?
Przez pomocniczy wrapper, który ukrywa rzeczywisty typ-implementację.
struct AnyStorage<T>: Storage { private let _add: (T) -> Void init<S: Storage>(_ storage: S) where S.Element == T { _add = storage.add } func add(_ item: T) { _add(item) } }
Czym różni się associatedtype od ogólnego parametru protokołu?
associatedtype definiuje konkretny typ, który musi być zaimplementowany zgodnie, podczas gdy ogólny parametr jest określany jawnie w samym protokole lub funkcji, ale nie może być użyty w deklaracji protokołu. Protokół nie może być ogólny syntaktycznie, tylko przez associatedtype.
Programista próbował użyć [Storage] do przechowywania dowolnych kolekcji. Kod nie kompiluje się, trzeba robić niejawne rzutowania lub używać podejść Any/unsafe.
Zalety:
Wady:
Programista uformował AnyStorage<T>, aby ukryć konkretną implementację, dodał ograniczenia where, aby zapewnić poprawne działanie tylko z potrzebnymi typami.
Zalety:
Wady: