KVO to skrót od Key-Value Observing i jest jednym z ważniejszych wzorców stosowanych w bibliotece Cocoa. W dużym skrócie wzorzec ten pozwala na obserwowanie zmian zachodzących w obiekcie o przykładowej nazwie „AAA”, przez obiekt „BBB”, który „zapisał” się na listę zainteresowanych lub (używając innego określenia) rozpoczął obserwowanie obiektu „AAA”. To tak na rozgrzewkę, w dalszej części sprawdzimy co dokładnie się za tym kryje 😏.

 

 

Podstawy

 

U podstaw KVO leży protokół o nazwie NSKeyValueObserving, który określany jest mianem „nieformalnego protokołu” (informal protocol). Termin informal protocol wywodzi się jeszcze z czasów Objective-C (brzmi jakby to było 30 lat temu 😝) i oznacza kategorię (category) przypisaną do obiektu NSObject (kategoria w Objective-C jest odpowiednikiem extension w Swift). Wszystkie zawarte w niej funkcje są opcjonalne, co oznacza, że decyzja o ich implementacji została pozostawiona programiście. To z kolei sprawia, że niemal każdy obiekt dziedziczący po NSObject jednocześnie spełnia wymogi protokołu NSKeyValueObserving (w języku angielskim mówimy, że „object conforms to the protocol”).

Wszystkie obiekty umieszczone w bibliotece Foundation dziedziczą po NSObject, a co za tym idzie korzystają z protokołu NSKeyValueObserving. W przypadku UIKit sprawa nie jest już tak oczywista. W dokumentacji Apple możemy tylko znaleźć wzmiankę, że obiekty z biblioteki UIKit „generalnie” nie wspierają KVO, ale możemy bez przeszkód implementować to rozwiązanie w naszych własnych obiektach. „Generalnie” oznacza w tym wypadku, że część obiektów z biblioteki UIKit może wspierać KVO, ale żeby to potwierdzić / zaprzeczyć będziemy musieli sprawdzić w dokumentacji każdy obiekt z osobna.

Przed pojawieniem się Swift w wersji 4, wielu programistów narzekało na trudności w użyci API implementującego KVO. Ja jednak zacząłem korzystać z tego rozwiązania dopiero niedawno, więc nie mogę niestety zająć jednoznacznego stanowiska. Ciągle świeżak jestem 😂.

 

 

 

Key-Value Observing na przykładzie

 

 

Poniższy przykład możecie sobie dodać do Playground. Jest on bardzo prosty, ale doskonale pokazuje o co tak naprawdę w KVO chodzi:

 

 

Na początku tworzymy przykładową klasę ObservedObject. Waszą uwagę na pewno zwróciło słowo kluczowe @objcMembers. Przed wprowadzeniem Swift 4 każdy obiekt dziedziczący po NSObject domyślnie dziedziczył również atrybut @objc, który jasno określał, że oznaczone nim pole obiektu lub funkcja / metoda do prawidłowego działania będzie potrzebowała środowiska wykonawczego Objectvie-C. To taki mechanizm, za pomocą którego Apple łączy nowe (Swfit) ze starym (Objective-C). KVO nie jest w stanie działać bez środowiska wykonawczego Objective-C.

W Swift 4 wprowadzono jednak pewne zmiany i atrybut @objc nie jest już dziedziczony z automatu. W nowej wersji musimy jasno określić za pomocą @objcMembers, że elementy naszego obiektu (oraz obiektów dziedziczących) będą wymagały środowiska wykonawczego Objective-C (przyda nam się to przy wyjaśnianiu parametru dynamic). W dokumentacji Apple możemy również znaleźć wzmiankę o tym, że implementacja atrybutu @objc może zwiększyć rozmiar skompilowanej aplikacji oraz negatywnie wpłynąć na jej wydajność. Dlatego @objcMembers najlepiej używać tylko w przypadku, gdy wszystkie pola / metody danego obiektu muszą korzystać z atrybutu @objc.

Słowo kluczowe dynamic poprzedzające zmienną observMe, podobnie jak @objc, jest związane ze środowiskiem wykonawczym Objective-C. Dzięki niemu mamy możliwość skorzystania z dynamic dispatch, specyficznej funkcji zarezerwowanej właśnie dla Objective-C. W Swift wybór implementacji danej metody / funkcji dokonywany jest już podczas kompilacji naszego programu. Jeżeli na przykład klasa pochodna nadpisuje (override) określoną metodą klasy bazowej, to środowisko wykonawcze Swift w trakcie kompilacji podejmie decyzję (na podstawie kontekstu), którą z metod (klasy bazowej lub pochodnej) należy użyć. W Objective-C podejście znacznie się różni. Dynamic dispatch już w trakcie działania programu (runtime) decyduje, która implementacja funkcji / metody ma zostać wykorzystana.

Ogólnie rzecz biorąc zagadnienia związane ze środowiskiem wykonawczym Objective-C / Swfit nie należą do najłatwiejszych, więc nie przejmujcie się jeżeli nie od razu wszystko zrozumiecie. Oczywiście podany wyżej opis jest jedynie dużym uproszczeniem, a po więcej informacji musicie sięgnąć do dokumentacji lub poszukać w innych miejscach w sieci.

Wracając na właściwe tory.  Zapis „\.observMe” jest po prostu skrótem od ObservedObject.observMe. Określa on jakie pole w danej klasie powinno być obserwowane. To właśnie ta ścieżka określana jest mianem key-path. Dzięki wprowadzeniu klasy generycznej KeyPath w iOS 11 wykorzystanie tej metody zostało znacznie uproszczone. W Swift 3 zapis wyglądał mniej więcej tak:

 

 

Powyższy zapis podczas kompilacji był sprawdzany pod względem poprawności (czy dana ścieżka faktycznie istnieje). Jeżeli wynik był pozytywny ścieżka była zamieniana na typ String (ponownie, na potrzeby środowiska wykonawczego Objective-C). W Swift 4 rzecz ma się podobnie, z tą różnicą, że teraz nie musimy pisać tak długich ścieżek, a dodatkowym atutem jest fakt, że już w trakcie pisania kodu mamy pewność, że dana ścieżka istnieje.

Na samym końcu przykładu prezentowany jest cały sens tego zamieszania. Skoro zmienna observMe została dodana do obserwowanych, to każda operacja na niej wykonana spowoduje wywołanie funkcji umieszczonej w zmiennej observation. W naszym przypadku w konsoli zostanie wyświetlona nowa wartość zmiennej observMe.

Warto również w tym miejscu dodać, że w Swift 3 koniecznym było „wypisanie się” danego obiektu z obserwowania określonej wartość w bloku deinit. W Swift 4 operacja ta jest wykonywana automatycznie.

I to by było na tyle. KVO jest bardzo przydatnym mechanizmem, ale jak każde zagadnienie w programowaniu wymaga praktyki i „obycia”. Sprawę niestety komplikuję trochę fakt, że KVO w Swift działa na wysokim poziomie abstrakcji (ukrywa konieczność korzystania z właściwości języka Objective-C) i trzeba sporo pogrzebać żeby naprawdę zrozumieć jak to wszystko działa. Ale przynajmniej wykonaliście już krok w dobrym kierunku 😎.

 


 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *