Piszemy aplikację Tyflopodcastu dla systemu iOS! Vol. 2

Tworzenie i konfiguracja projektu (CD)

Atrybut Typ Opis
Excerpt String Opis danego zasobu (WordPressowa Zajawka)
hasVisited Bool Czy użytkownik przeczytał/odsłuchał artykuł/podcast?
id UUID Unikalny identyfikator zasobu
name String Nazwa danego zasobu
url String Adres URL danego zasobu
Atrybuty

Nie bez kozery atrybuty przedstawiłem w formie tabeli. Nie dość, że takie dane są sporo prostsze do przyswojenia przez człowieka w takiej formie, to jeszcze komputer również przechowuje je właśnie tak. Jak ktokolwiek miał do czynienia z relacyjnymi bazami danych (SQL), to wie, o co chodzi, a jeżeli nie, to zapraszam tutaj. Kiedyś o bazach zrobię osobny wpis, bo nawet jak nie jesteśmy programistami to bazy danych to super zabawa i warto się z nimi moim zdaniem zapoznać. Tymczasem jednak przejdźmy dalej, bo wciąż sporo pracy przed nami…

Tworzenie kontrolera danych

Gdy szablon naszej bazy jest już gotowy, możemy przejść do stworzenia czegoś, co ja nazwę sobie kontrolerem danych. Zadaniem tego prostego kawałka kodu będzie przygotowanie naszej biblioteki do używania. Przygotowanie, niezaładowanie. Bo co jeżeli nasza biblioteka miałaby na przykład 20 tysięcy podcastów? Wiem, Tyflopodcast tyle nie ma, ale może mieć, więc takie problemy należy przewidywać już na początku procesu pisania aplikacji, bo potem ich rozwiązanie może przyprawić nas o ból głowy.

Aby utworzyć nowy plik, należy wpierw przejść do nawigatora (wspominałem o nim krótko) przy pomocy skrótu Command + 1. Następnie z drzewka należy wybrać grupę o nazwie Tyflocentrum i w reszcie wcisnąć skrót CMD + N (New). Spowoduje to otwarcie okna tworzenia nowego pliku, które bardzo mocno przypomina to użyte przez nas wcześniej do stworzenia aplikacji. W siatce “Template chooser” wybieramy “Swift file” i klikamy “Next”. Jako nazwę wprowadzamy “DataController.swift” i klikamy “Save.

Edytor automatycznie otworzy nasz nowy plik, który wygląda tak.

//

//  DataController.swift
//  Tyflocentrum
//
//  Created by Arkadiusz Świętnicki on 12/10/2022.
//

import Foundation

Jak widzimy, plik ten jak na razie nie zawiera w sobie wiele treści. Obecnie możemy wyróżnić w nim tylko dwa elementy.

  1. Linijki rozpoczynające się od dwóch znaków ukośnika (//). Taki zapis oznacza komentarz. Komentarz w programowaniu to nic innego jak znak dla komputera mówiący “Olej to, to cię nie dotyczy”.
  2. import Foundation: konstrukt import XYZXYZABC pozwala nam dodać do naszego programu jakąś funkcjonalność. Z polskiego na nasze gdy piszemy "import" to każemy komputerowi "zrekrutować" jakiś framework czyli jakąś funkcjonalność.

A propo konstruktu import to ustawiamy się w edytorze tuż przed linijką import Foundation i robimy przed nią nową linię klawiszem Return. Następnie własnymi paluszkami wprowadzamy frazę import CoreData. Jak chyba wcześniej wspominałem, „CoreData” jest frameworkiem służącym do wykonywania operacji na bazach danych.

Nie musimy importować bibliotek alfabetycznie, ale gwarantuję Wam, że przy bardziej skomplikowanych projektach taka praktyka może się nam bardzo przydać.

Idąc dalej, musimy utworzyć klasę. Klasa to nic innego jak swego rodzaju szablon, który opisuje jakiś mniej lub bardziej abstrakcyjny obiekt, na przykład Kotek, Auto czy… model danych. Na Wasze (i chyba moje też) nieszczęście akurat teraz mamy obiekt bardziej abstrakcyjny. No bo jak w świecie rzeczywistym możemy opisać kontroler danych? UFO na siedmiu nogach?

Na końcu naszego pliku dopisujemy sobie coś takiego.

class DataController: ObservableObject {

I znowu teoria (wiem, jak bardzo mnie kochacie). Ten króciutki fragment składa się aż z czterech możliwych do wyróżnienia części opisanych poniżej.

  • class: kolejne już słowo kluczowe języka programowania Swift. Informuje ono komputer, że to, co następuje po nim, dotyczy klasy, a nie na przykład zmiennej, nazwy biblioteki czy komentarza.
  • DataController
  • To akurat nic innego jak moja własna nazwa dla klasy. Jest kilka sztywnych zasad nazywania klas, zmiennych i struktur, ale teraz nie o tym.
  • ObservableObject: to jest już bardziej skomplikowana sprawa, albowiem jest to protokół, czyli kolejna bardzo ważna funkcja języka Swift. Protokoły to nic innego jak kontrakty czy umowy zawierane między nami — programistami
  • a językiem Swift. Taka umowa oznacza, że my, a właściwie nasza klasa musi spełnić pewne wymagania, w zamian za co Swift “gwarantuje nam” możliwość wykonania naszego kodu.
  • (Lewy nawias klamrowy: mówi komputerowi, że rozpoczyna się nowy blok. Blok to nic innego jak związany ze sobą kontekstowo fragment kodu, w naszym przypadku klasa.

UWAGA!

Jeżeli w poprzedniej części, w paragrafie poświęconym konfiguracji środowiska XCode nie wyłączyliście automatycznego uzupełniania nawiasów to program automatycznie wstawił też prawy nawias klamrowy, oznaczający po prostu koniec bloku.

Następnie w środku naszego bloku wpisujemy takie o to magiczne zaklęcie:

let container = NSPersistentContainer(name: "Tyflocentrum")

Tutaj też jest kilka możliwych do wyróżnienia części, ale tym razem omówimy tylko te najważniejsze

  • let: Piekielnie ważne słówko kluczowe języka Swift, oznaczające stałą. Stałaj może być ustawiona raz, a potem nie może być zmieniana, i dlatego jest stałą a nie zmienną. 😀
  • NSPersistentContainer: To jest nazwa klasy importowanej przez CoreData. Tutaj przydałoby się trochę historii w celu wyjaśnienia nazwy. Apple, a raczej NextStep kombinowali już z frameworkami do baz danych od bardzo długiego czasu. NS w tej nazwie oznacza po prostu NextStep, czyli nazwę systemu operacyjnego na podwalinach którego powstał współczesny MacOS i w konsekwencji tego iOS. Core Data powstało wraz z iPhone OS 3 (to wtedy NIE był iOS!) To stąd ta archaiczna nazwa.
  • name: “Tyflocentrum”: Kolejny niezwykle ważny konstrukt Swifta. Są to tak zwane parametry nazwane (named parameters po angielsku). Jak ktoś miał do czynienia z programowaniem to jest przyzwyczajony raczej do czegoś takiego: void pozdrow(const wchar_t *kogo). W Swifcie jest inaczej (o tym zaraz).

No i elegancko. Pewnie czujecie się jak na średnio ciekawej lekcji matematyki w szkole, gdzie nauczycielka swoje, wasze ręce swoje a mózg już zjada pizzę w jakiejś eleganckiej Włoskiej restauracji. Ta długa, strasznie brzmiąca linijka przygotowuje po prostu naszą bazę danych do użycia.

Następnym krokiem jest stworzenie własnego konstruktora (initializer po angielsku). Konstruktor to zestaw instrukcji jakie są wykonywane po “narodzinach” obiektu. Jak rodzi się dziecko to instrukcją, którą ma lekarz jest odcięciem pępowiny. Nasza instrukcja będzie mniej spektakularna, i po narodzinach kontrolera danych biblioteka będzie się wczytywała. Poniżej kod z notatkami, jak to ja mam w zwyczaju


init() { // Tworzymy konstruktor
		container.loadPersistentStores { description, error in // Ładujemy nasze dane. Jako, że jesteśmy pesymistami to zakładamy, że coś pójdzie nie tak.
			if let error = error { // Jeżeli jest błąd, to...
				fatalError("Failed to load the data model!\n\(error.localizedDescription)") // Z takiego błędu nie można się wykaraskać. Jedyne co to możemy pokazać błąd.
			}
		}
	}
}

W tym kodzie jest bardzo dużo rzeczy do omówienia, albowiem wjeżdżają między innymi funkcje anonimowe, lambdy, predykaty… zwał to jak zwał, w każdym razie o co chodzi z linijką container.LoadPersistentStores {description, error in

Zapis może brzmieć strasznie ale w gruncie rzeczy koncept jest bardzo prosty. Chodzi bowiem o to, że funkcja LoadPersistentStores jako swój parametr przyjmuje coś, co Apple nazywa “Completion Handler”. Ten “Completion handler” to funkcja, która wykonuje się po wykonaniu swojej funkcji macierzystej, w tym wypadku nasze ukochane LoadPer`sistentStores. Z kolei tamta funkcja przyjmuje dwa parametry, odpowiednio description i error. Nasz zapis jest po prostu skrótem. Nie będę już Wam mieszał ale ten completion handler może “żyć” w zupełnie innym miejscu kodu.

następna linijka może być równie czarnomagiczna, jak ja uczyłem się Swifta to długo zastanawiałem się po co to komu i na co, także jeżeli moje tłumaczenie do Was nie trafia, to zapraszam do kontaktu 😉

if let error = error {

Tę linijkę można wyjaśnić na conajmniej dwa sposoby. Pierwszy z nich to kilka zdań, a drugi. jakieś 30, 40 stron maszynopisu. Pozwólcie, że dla waszego i mojego zdrowia psychicznego ogranicze się do pierwszego sposobu

Zapis ten oznacza nic więcej, niż tylko “Jeżeli istnieje błąd to…” W Swifcie jest sobie coś co nazywa się wartościami opcjonalnymi (Optionals po angielsku). Wartość może istnieć, albo nie istnieć. Na przykład jeżeli mamy zmienną

hoursWorked: Int = 42

To jest akurat proste, oznacza to że wartość przepracowanych godzin jest równa 42, nawet zapis hoursWorked: Int = 0 ma sens, ale zapis hoursWorked: Int = nil już taki oczywisty nie jest. Oznacza to tyle, że ktoś może w ogóle nie zamierza pracować, dlatego nie prowadzi indeksu przepracowanych godzin, dlatego wartość zwyczajnie nie istnieje. Tak samo jest w naszym przykładzie. Jeżeli wszystko poszło zgodnie z planem (baza danych została poprawnie wczytana), to błąd zwyczajnie NIE ISTNIEJE. W Swifcie ten mechanizm jest bardzo często stosowany, i nawet w tym projekcie spotkamy się z konstruktem if let blabla = blabla jeszcze z 10 razy dlatego nie ma, że boli i trzeba przyswajać.

Co ciekawe istnieje konstrukt niejako odwrotny do if let blabla = blabla, ale… o tym opowiem Wam już innym razem.

Zanim nasza baza danych będzie już tak na serio gotowa do działania musimy zrobić jeszcze jedną istotną rzecz, a mianowicie “wstrzyknąć” ją do środowiska. Chodzi o to, że program korzysta przeważnie tylko z jednej bazy danych, więc żeby nie trzebabyło nią “rząglować” pomiędzy konkretnymi oknami, możemy sprawić aby ta była dostępna globalnie dla całej aplikacji.

W tym celu otwieramy plik “TyflocentrumApp.Swift”.

Następnie udajemy się pod linijkę struct TyflocentrumApp: App { i tóż pod nią wpisujemy: @StateObject private var dataController = DataController()

Znowu w tym kodzie jest kilka rzeczy do wyjaśnienia, więc czym prędzej spieszę:

  • StateObject: To jest takie ustrojstwo, które pozwala nam utrzymywać bardziej skomplikowane obiekty przy życiu pomiędzy oknami. W tym wypadku jest to o tyle ważne, że nasza aplikacja będzie potrzebować dostępu do bazy danych z różnych miejsc.
  • private: to słowo kluczowe oznacza, że nasza zmienna będzie prywatna dla klasy/struktury, w której została stworzona. Z polskiego na nasze nie będziemy mogli dostać się do niej z zewnątrz.

Obiecuję, to już ostatni krok jeżeli chodzi o ustawianie naszej bazy danych. Teraz należy ustawić się na końcu linijki contentView() i dodać tę satanistyczną inwokację:

.environment(\.managedObjectContext, dataController.container.viewContext)

2 komentarze

  1. Wiem że nikt tego nie czyta ale trzecia część w drodze, a będzie się działo, oj będzie. Przepisywaliśmy obsługę sieci, bo pomyliłem parę rzeczy i działało powoli, a nawet pokaże Wam, jak dodać możliwośc puszczania sobie podcastów ze skrótów czy Siri

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

EltenLink