Dopasowywanie wzorców (pattern matching) to fundamentalna cecha Swift, która sprawia, że kod jest bezpieczniejszy i czytelniejszy. Historycznie dopasowywanie wzorców rozwijało się jako środek do bardziej wyrazistej i bezpieczniejszej pracy z enum, szczególnie z wartościami skojarzonymi. W odróżnieniu od innych języków, Swift umożliwia rozpakowywanie zagnieżdżonych wartości bezpośrednio w wyrażeniach if, guard, switch.
Problem: w przypadku niestandardowych enum, szczególnie z zagnieżdżonymi wartościami skojarzonymi, łatwo popełnić błąd w strukturze case lub niedoszacować ograniczeń głównego switch. Często także nie jest oczywista różnica między użyciem if case, guard case i klasycznego switch.
Rozwiązanie: stosować różne podejścia do dopasowywania wzorców w zależności od scenariusza, a przy skomplikowanych zagnieżdżonych strukturach – wyraźnie wskazywać nazwy i używać where.
Przykład kodu:
enum NetworkState { case success(User) case failure(Error, code: Int) case loading(progress: Double) } let state = NetworkState.failure(SomeError(), code: 401) if case let .failure(error, code) = state, code == 401 { print("Unauthorized: \(error)") } guard case .success(let user) = state else { return } print(user) switch state { case .success(let user): print("Welcome, \(user.name)") case .failure(let error, let code) where code == 404: print("Not Found: \(error)") case .failure(_, let code): print("Failure with code: \(code)") case .loading(let progress): print("Progress: \(progress)") }
Kluczowe cechy:
Co się stanie, jeśli nie określisz wszystkich case enum w switch?
Błąd kompilacji (jeśli enum non-optional) lub ostrzeżenie/wymaganie default case, jeśli nie wszystkie pokrycia zostały uwzględnione. Dla Optionals wystarczy określić tylko .some i .none.
Czy można robić dopasowywanie wzorców z zagnieżdżonymi enum i wartościami skojarzonymi?
Tak. Ale składnia staje się bardziej skomplikowana. Dla zagnieżdżonych wartości można od razu rozpakować kilka poziomów:
enum Outer { case inner(Inner) } enum Inner { case item(id: Int) } let e = Outer.inner(.item(id: 5)) if case let .inner(.item(id)) = e { print(id) }
Czym różni się guard case od if case podczas dopasowywania wzorców?
guard case sprawdza warunek i wykonuje blok wyjścia (return, throw, break) w razie niepowodzenia. Zwykle jest używany do wymagań: jeśli nie pasuje, wyjdź.
if case – konstrukcja odpowiednia do jednolinijkowych warunkowych obsługujących bez wychodzenia z funkcji.
Programista używa switch dla enum z wieloma case, ale nie pokrywa nowego case. Kod kompiluje się, ale nowy scenariusz nie jest obsługiwany, a aplikacja pada w czasie wykonywania.
Zalety:
Wady:
Programista jawnie pokrywa wszystkie case, używa where do lokalnych filtrów, rozpakowuje zagnieżdżone wartości przez zagnieżdżone let.
Zalety:
Wady: