W projekcie, nad którym aktualnie pracuję pojawiła się konieczność wprowadzenia prostej implementacji lokalnej bazy danych, więc pomyślałem, że przy okazji napiszę krótki artykuł jak należy zacząć pracę z wybraną technologią. W przypadku iOS (będzie też wersja na Androida, ale to inna para kaloszy) do wyboru miałem Core Data (od Apple) lub Realm, który jest bardzo popularną biblioteką open source. Każde z tych rozwiązań ma swoje wady i zalety, a w internecie możecie znaleźć sporo porównań. Ja nie będę rozwodził się nad różnicami, bo zwyczajnie nie mam żadnego doświadczenia z Realm.

W przyszłości mam zamiar bliżej się mu przyjrzeć, ale na chwilę obecną mam więcej doświadczenia z Core Data, które to już implementowałem we własnych, hobbystycznych aplikacjach. Tym razem jednak chciałem podejść do tematu z trochę innej strony i lepiej przyjrzeć się podstawowym obiektom z których składa się tzw. Core Data Stack. Dlatego zamiast całkowicie polegać na Xcode i jego automatycznej implementacji, postanowiłem wprowadzić wszystko „ręcznie”. Na samym końcu tego artykułu znajdziecie link, za pomocą którego możecie pobrać prostą aplikację prezentującą w praktyce wszystkie elementy opisane w tym artykule.

 

 

Czym jest Core Data Stack?

 

W dużym uproszczeniu jest to zestaw 5 obiektów, które pozwalają na bezproblemowe korzystanie z Core Data. Zwykle jednak część z nich jest przed nami ukrywana, z racji tego, że często decydujemy się na automatyczną implementację, którą oferuje Xcode (np. podczas tworzenia nowego projektu). Bliższe poznanie elementów składających się na całość układanki pomoże nam jednak w bardziej efektywnym oraz wydajnym wykorzystaniu Core Data. A optymalizacja to honorowy obowiązek każdego inżyniera 😎 (i nie mam na myśli tutaj tytułu naukowego 🤓).

Proponuję przy pierwszym podejściu rzucić tylko okiem na część teoretyczną i od razu zabrać się przeglądanie przykładowej apki. Kiedy  już sprawdzicie jak wszystko działa w praktyce to łatwiej będzie Wam przyswoić suchą teorię.

 

PERSISENT STORE CONTAINER

 

Jest to nowość wprowadzona od iOS 10. Dobrze pasuje tutaj określenie „One to rule them all”. Rolą obiektu o nazwie NSPersistentContainer jest zarządzanie wszystkimi obiektami wchodzącymi w skład stack’u Core Data. Można nazwać go spoiwem, które wykonuje dużą część pracy za nas. Będzie on odpowiedzialny za utworzenie obiektów NSManagedObjectModelNSPersistentStoreCoordinator oraz NSManagedObjectContext (o których więcej za chwilę). Jak to będzie sprawdzało się w praktyce możecie sprawdzić w przykładowej aplikacji.

 

MANAGED OBJECT MODEL

 

Obiekty NSManagedObjectModel odpowiadają za reprezentację modelu naszych danych w bazie. Wykorzystują do tego zwykłe klasy, które zwierają odpowiednie pola określające właściwości danego obiektu oraz jego relacji z innymi obiektami w bazie (w postaci NSSet lub NSOrderedSet). To dzięki nim pozostałe elementy ze stack’u Core Data wiedzą jak należy tworzyć poszczególne obiekty i jak nimi zarządzać (zapisywać / odczytywać). W pewnym uproszczeniu można obiekty te porównać do schematów baz danych (to dla tych, którzy znają trochę SQL), ale należy pamiętać, że Core Data może pod maska korzystać również z innego sposobu zapisu danych niż baza SQL. O tym jakie to sposoby będzie za chwilę 😏.

 

 

PERSISTENT STORE

 

 

Obiekt NSPersistentStore odpowiada za zapis / odczyt danych z „magazynu danych”, który zdecydowaliśmy się wykorzystać. Domyślnie dostępne są trzy warianty atomic oraz jeden non-atomic. Atomic oznacza, że dane znajdujące się w bazie danych muszą zostać w pełni zdeserializowane (to chyba nie jest do końca po polsku 😜), a następnie w całości załadowane do pamięci urządzenia, zanim będziemy mogli wykonać na nich jakiekolwiek operacje. Natomiast implementacje korzystające z non-atomic umożliwiają ładowanie danych do pamięci we fragmentach. Jak się pewnie domyślacie bardziej wydajna jest ta druga opcja. W jej przypadku szybciej uzyskamy dostęp do potrzebnych nam danych oraz w znacznie mniejszym stopniu będziemy obciążać ograniczone zasoby urządzenia (inżynier-nija czuwa 😎).

Do wyboru mamy następujące rodzaje magazynów danych:

1. NSQLiteStoreType – domyślna implementacja korzystająca z dobrze wielu znanej bazy SQL. Jest to jedyna wbudowana opcja typu non-atomic. Czyni ją to w większości wypadków najlepszym wyborem (pewnie dlatego jest to opcja domyślna 😂).

2. NSBinaryStoreType  – w tym przypadku dane są składowane w postaci pliku binarnego. NSBinaryStoreType korzysta z implementacji atomic, co oznacza, że przed właściwym użyciem cały plik musi zostać załadowany do pamięci.

3. NSXMLStoreType  – dane przechowywane są w postaci pliku XML. Ta opcja dostępna jest tylko w przypadku pisania aplikacji dla systemu OSX (iOS odpada).

4. NSInMemoryStoreType – bardzo ciekawa propozycja. W tym przypadku dane nie są tak na prawdę zapisywane na stałe na dysku, a tylko tymczasowo w pamięci. Ja nie znalazłem zastosowania dla takiego sposobu zapisu danych, ale z całą pewnością można zrobić z tego jakiś użytek. NSInMemoryStoreType korzysta z implementacji typu atomic.

 

PERSISTENT STORE COORDINATOR

 

Obiekt NSPersistentStoreCoordinator stanowi pomost pomiędzy NSManagedObjectModel oraz NSPersistentStore. To na jego barkach spoczywa znaczna część ciężkiej pracy. Odpowiada on za właściwą komunikację pomiędzy NSManagedObject oraz NSPersisntentStoreNSPersistentStoreCoordinator działa na wyższym poziomie abstrakcji – w tym sensie, że ukrywa przed nami szczegóły implementacji wybranego przez nas „magazynu danych”. Dzięki temu obiekt NSManagedObjectContext (o nim za chwilę) nie musi zastanawiać się gdzie będzie zapisywał dane. A jeżeli zdarzy się tak, że będziemy chcieli skorzystać jednocześnie z kilku możliwości trwałego zapisu (co jest w przypadku Core Data możliwe), to za pomocą NSPersistentStoreCoordinator obiekt NSManagedObjectContext będzie miał dostęp do ujednoliconego interfejsu.

 

MANAGED OBJECT CONTEXT

 

Zostawiłem go na sam koniec, ale w rzeczywistości to właśnie z tym obiektem będziemy współpracowali najczęściej. NSManagedObjectContext bardzo często porównywany jest do brudnopisu (angielska nazwa to scratchpad), ale chyba łatwiej wyobrazić go sobie w formie brudnopisu cyfrowego:

 

 

NSManagedObjectContext jest takim brudnopisem w pamięci urządzenia, w którym zapisujemy sobie różne operacje związane z naszą bazą danych. Jeżeli zmiany, które wprowadziliśmy będą nam odpowiadały to możemy zapisać je na dysku (za pomocą funkcji save()). Jeżeli jednak się na to nie zdecydujemy to wszystkie wprowadzone zmiany zostaną odrzucone (nie będę zapisane w bazie danych / na dysku). To tak jakby wyrwać kartkę z brudnopisu i wyrzucić ją do kosza.

NSManagedObjectModel nie może funkcjonować bez przypisanego mu obiektu NSManagedObjectContext, ponieważ ten zarządza jego cyklem życia. Operacje na obydwu powyższych obiektach można wykonywać tylko w wątku, w którym zostały utworzone, co oznacza, że nie są one „wątkowo-bezpieczne” (thread-safe). Jeżeli ktoś zna lepsze tłumaczenie tego zwrotu to chętnie się z nim zapoznam 🤨. I na koniec jako ciekawostkę podam, że można korzystać jednocześnie z kliku obiektów NSManagedObjectContext, ale wyjaśnienie tego wykracza poza potrzeby tego wpisu.

 

Słowo na drogę

 

To było takie szybkie wprowadzenie w podstawową teorię związaną z Core Data Stack. Poniżej umieszczam link, za pomocą którego możecie pobrać bardzo prostą aplikację prezentującą zastosowanie powyższej teorii. Do usłyszenia 🧐.

 

Link do aplikacji

 


 

 

 

Comments

Dodaj komentarz

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