rename Algorytm to Program
This commit is contained in:
@@ -8,7 +8,7 @@ Przygotowana implementacja bazuje na architekturze Model-View. Jest to podejści
|
||||
Warstwa prezentacji aplikacji opisująca początkowy interfejs użytkownika jest zaimplementowana w `AllNotesScreen`. Ekran jest widoczny dla użytkownika, gdy ustawił swoją nazwę użytkownika w sieci peer-to-peer. Składa się z listy podzielonej na dwie sekcje - notatek których użytkownik jest autorem, oraz notatek do których użytkownik został zaproszony. Nad listą znajduje się przycisk stworzenia nowej notatki. Naciśnięcie na którąkolwiek z istniejących notatek prowadzi do otwarcia jej zawartości. W zależności od tego, czy użytkownik jest właścicielem danej notatki czy współtwórcą, interfejsem do edycji tej notatki są odpowiednio obiekty `NoteEditorScreen` oraz `SharedNoteEditor`. Z ekranu `NoteEditorScreen` możemy przejść do `ManageMembersScreen`, który jest listą użytkowników zaproszonych i możliwych do zaproszenia do edytowania aktualnej notatki.
|
||||
|
||||
== Model danych
|
||||
Algorytm 3.1 przedstawia podstawowy obiekt reprezentujący notatkę w systemie plików - `Note`. Składa się on z URL (Universal Resource Locator), czyli ścieżki prowadzącej do pełnej zawartości notatki użytkownika, która jest przechowywana w pliku tekstowym o rozszerzeniu `txt`. Dodatkowo przechowujemy w tym obiekcie jeszcze `name`, które pełni funkcję ułatwionego dostępu do przyjaznej nazwy, jednocześnie będąc nazwą pliku w lokalnym systemie plików oraz przyjazną nazwą prezentowaną w liście notatek do których użytkownik został zaproszony. Obiekt implementuje protokół `Identifiable`, który gwarantuje możliwość identyfikacji obiektu w zbiorze zawierającym wiele jego instancji, np. w tablicy. Ta cecha jest wymagana i wykorzystywana przez SwiftUI, by móc rozróżniać rodzaje zmian na komponentach interfejsów graficznych zawierających wiele kopii takiego obiektu, np. `List` lub `ForEach`. Umożliwia to dokonanie decyzji co do sposobu animacji zmian na ekranie użytkownika, ponieważ framework będzie mógł rozróżnić usunięcie i wstawienie nowego obiektu, od zmiany parametrów tej samej instancji.
|
||||
Program 3.1 przedstawia podstawowy obiekt reprezentujący notatkę w systemie plików - `Note`. Składa się on z URL (Universal Resource Locator), czyli ścieżki prowadzącej do pełnej zawartości notatki użytkownika, która jest przechowywana w pliku tekstowym o rozszerzeniu `txt`. Dodatkowo przechowujemy w tym obiekcie jeszcze `name`, które pełni funkcję ułatwionego dostępu do przyjaznej nazwy, jednocześnie będąc nazwą pliku w lokalnym systemie plików oraz przyjazną nazwą prezentowaną w liście notatek do których użytkownik został zaproszony. Obiekt implementuje protokół `Identifiable`, który gwarantuje możliwość identyfikacji obiektu w zbiorze zawierającym wiele jego instancji, np. w tablicy. Ta cecha jest wymagana i wykorzystywana przez SwiftUI, by móc rozróżniać rodzaje zmian na komponentach interfejsów graficznych zawierających wiele kopii takiego obiektu, np. `List` lub `ForEach`. Umożliwia to dokonanie decyzji co do sposobu animacji zmian na ekranie użytkownika, ponieważ framework będzie mógł rozróżnić usunięcie i wstawienie nowego obiektu, od zmiany parametrów tej samej instancji.
|
||||
|
||||
#let note_struct = [```swift
|
||||
struct Note: Identifiable {
|
||||
@@ -25,7 +25,7 @@ struct Note: Identifiable {
|
||||
caption: [Definicja obiektu notatki],
|
||||
)
|
||||
|
||||
Algorytm 3.2 przedstawia `NoteMessage` - obiekt zawierający zawartość notatki, którą wysyłamy do użytkowników, którzy przyjęli zaproszenie do edycji notatki. Znajdziemy w niej pola `SenderID`, które jest identyfikatorem właściciela notatki oraz `content`, czyli właściwą zawartość notatki. Jest ona również używana do wysyłania każdej aktualizacji do wszystkich użytkowników. Obiekt implementuje protokół `Codable`, który jest uniwersalnym interfejsem kodowania danych. Daje to nam wbudowane wsparcie kodowowania do formatów JSON i XML. Mamy możliwość również pisania własnych koderów, które umożliwią zamianę obiektów implementujących `Codable` do wybranych przez nas, innych formatów danych.
|
||||
Program 3.2 przedstawia `NoteMessage` - obiekt zawierający zawartość notatki, którą wysyłamy do użytkowników, którzy przyjęli zaproszenie do edycji notatki. Znajdziemy w niej pola `SenderID`, które jest identyfikatorem właściciela notatki oraz `content`, czyli właściwą zawartość notatki. Jest ona również używana do wysyłania każdej aktualizacji do wszystkich użytkowników. Obiekt implementuje protokół `Codable`, który jest uniwersalnym interfejsem kodowania danych. Daje to nam wbudowane wsparcie kodowowania do formatów JSON i XML. Mamy możliwość również pisania własnych koderów, które umożliwią zamianę obiektów implementujących `Codable` do wybranych przez nas, innych formatów danych.
|
||||
|
||||
#let note_message_struct = [```swift
|
||||
struct NoteMessage: Codable {
|
||||
@@ -40,7 +40,7 @@ struct NoteMessage: Codable {
|
||||
caption: [Definicja obiektu notatki wysyłanego między użytkownikami],
|
||||
)
|
||||
|
||||
Do reprezentacji zaproszeń stworzyłem obiekt `NoteInvitation` przedstawiony w algorytmie 3.3. Przechowuje on informacje potrzebne do obsługi całego procesu zaproszeń między użytkownikami systemu. Składa się z `invitatorID`, który jest wyspecjalizowanym obiektem frameworku `MultipeerConnectivity` i umożliwia identyfikację użytkownika w czasie komunikacji peer to peer. `note` to obiekt reprezentujący notatkę, którą otrzymujemy w zaproszeniu. Składa się on z tytułu i treści, które były aktualne w momencie zapraszania użytkownika. Ostatnim i jednocześnie prywatnym parametrem jest `invitationHandler`, który jest typem funkcji przyjmującej wartość boolowską (prawda/fałsz). Jest on wykorzystywany do wstrzyknięcia logiki akceptacji notatki, która wymaga kilku operacji w logice klasy obsługującej przyjmowanie zaproszeń. Dzięki takiemu podejściu tworzymy luźną zależność do skomplikowanego obiektu na dalszych etapach systemu. `NoteInvitation` zawiera również metody `accept()` oraz `decline()`, które odpowiadają za wykonanie akcji zaproszenia, które pod spodem odpowiednio używają parametru `invitationHandler`, który można wykonać tylko raz, ze względu na specyfikę `Multipeer Connectivity`, a następnie usuwamy go z pamięci.
|
||||
Do reprezentacji zaproszeń stworzyłem obiekt `NoteInvitation` przedstawiony w programie 3.3. Przechowuje on informacje potrzebne do obsługi całego procesu zaproszeń między użytkownikami systemu. Składa się z `invitatorID`, który jest wyspecjalizowanym obiektem frameworku `MultipeerConnectivity` i umożliwia identyfikację użytkownika w czasie komunikacji peer to peer. `note` to obiekt reprezentujący notatkę, którą otrzymujemy w zaproszeniu. Składa się on z tytułu i treści, które były aktualne w momencie zapraszania użytkownika. Ostatnim i jednocześnie prywatnym parametrem jest `invitationHandler`, który jest typem funkcji przyjmującej wartość boolowską (prawda/fałsz). Jest on wykorzystywany do wstrzyknięcia logiki akceptacji notatki, która wymaga kilku operacji w logice klasy obsługującej przyjmowanie zaproszeń. Dzięki takiemu podejściu tworzymy luźną zależność do skomplikowanego obiektu na dalszych etapach systemu. `NoteInvitation` zawiera również metody `accept()` oraz `decline()`, które odpowiadają za wykonanie akcji zaproszenia, które pod spodem odpowiednio używają parametru `invitationHandler`, który można wykonać tylko raz, ze względu na specyfikę `Multipeer Connectivity`, a następnie usuwamy go z pamięci.
|
||||
|
||||
#let note_invitation_struct = [```swift
|
||||
struct NoteInvitation: Identifiable {
|
||||
@@ -83,7 +83,7 @@ struct NoteInvitation: Identifiable {
|
||||
caption: [Definicja obiektu reprezentującego zaproszenie do edycji notatki],
|
||||
)
|
||||
|
||||
Ostatnim obiektem jest struktura `Peer` przedstawiona w algorytmie 3.4, która jest opisem aktualnego stanu połączenia wykrytego innej instancji systemu w pobliżu użytkownika. Składa się z wartości enumerowanej opisującej stan połączeniao raz identyfikatorem użytkownika w sieci peer to peer. Implementuja ona protokół `Idenfitiable`, by móc zostać poprawnie użyta do rysowania listy dostępnych klientów w pobliżu użytkownika.
|
||||
Ostatnim obiektem jest struktura `Peer` przedstawiona w programie 3.4, która jest opisem aktualnego stanu połączenia wykrytego innej instancji systemu w pobliżu użytkownika. Składa się z wartości enumerowanej opisującej stan połączeniao raz identyfikatorem użytkownika w sieci peer to peer. Implementuja ona protokół `Idenfitiable`, by móc zostać poprawnie użyta do rysowania listy dostępnych klientów w pobliżu użytkownika.
|
||||
|
||||
#let peer_struct = [```swift
|
||||
struct Peer: Identifiable {
|
||||
@@ -110,7 +110,7 @@ struct Peer: Identifiable {
|
||||
Całość komunikacji między urządzeniami odbywa się z wykorzystaniem frameworka Multipeer Connectivity. Klient twórcy notatki pełni rolę serwera, a pozostali użytkownicy, po uprzednim zaproszeniu, mogą dołączyć do edycji notatki, wysyłać swoje zmiany jak i odbierać zmiany, które dystrybuuje serwer.
|
||||
|
||||
== Odkrywanie innych urządzeń
|
||||
Obiekt reprezentujący serwer został nazwany `NoteEditingSessionServer`, który dziedziczy właściowści po klasie `NSObject`, która jest uniwersalną implementacją wielu zachowań, które są wymagane od frameworków udostępnianych przez Apple, które zostały napisane w języku Objective-C. Jego konstruktor, przedstawiony w algorytmie 3.5, w przyjmowanych argumentach oczekuje tylko obiektu `OwnPeer`, który będzie wykorzystywany do identyfikacji instancji aplikacji u innych klientów. Sama implementacja konstruktora tworzy nową sesję `MCSession`; obiekt `MCNearbyServiceBrowser`, który odpowiada za wykrywanie pobliskich klientów. Finalnie przypisuje referencję do samego siebie jako parametr `delegate` dla utworzonych `MCSession` i `MCNearbyServiceBrowser`. Pozwala nam to zaimplementować metody, które będą wykorzystywane wewnątrz tych obiektów do komunikacji z innymi użytkownikami. Protokoły delegujące dla wspomnianych obiektów nazywają się odpowiednio `MCSessionDelegate` oraz `MCNearbyServiceBrowserDelegate`. Moja implementacja tych protokołów zostanie przedstawiona w dalszej części pracy.
|
||||
Obiekt reprezentujący serwer został nazwany `NoteEditingSessionServer`, który dziedziczy właściowści po klasie `NSObject`, która jest uniwersalną implementacją wielu zachowań, które są wymagane od frameworków udostępnianych przez Apple, które zostały napisane w języku Objective-C. Jego konstruktor, przedstawiony w programie 3.5, w przyjmowanych argumentach oczekuje tylko obiektu `OwnPeer`, który będzie wykorzystywany do identyfikacji instancji aplikacji u innych klientów. Sama implementacja konstruktora tworzy nową sesję `MCSession`; obiekt `MCNearbyServiceBrowser`, który odpowiada za wykrywanie pobliskich klientów. Finalnie przypisuje referencję do samego siebie jako parametr `delegate` dla utworzonych `MCSession` i `MCNearbyServiceBrowser`. Pozwala nam to zaimplementować metody, które będą wykorzystywane wewnątrz tych obiektów do komunikacji z innymi użytkownikami. Protokoły delegujące dla wspomnianych obiektów nazywają się odpowiednio `MCSessionDelegate` oraz `MCNearbyServiceBrowserDelegate`. Moja implementacja tych protokołów zostanie przedstawiona w dalszej części pracy.
|
||||
|
||||
#let note_editing_session_server_init = [```swift
|
||||
init(peer: OwnPeer) {
|
||||
@@ -129,7 +129,7 @@ init(peer: OwnPeer) {
|
||||
caption: [Implementacja konstruktora obiektu NoteEditingSessionServer],
|
||||
)
|
||||
|
||||
W momencie, gdy autor notatki otworzy ekran edycji, wykonuje się metoda `startServer()`, która wywołuje metodę `startBrowsingForPeers()` obiektu `MCNearbyServiceBrowser`. Opuszczenie ekranu edycji wywołuje metodę `stopServer()`, która wywołuje analogiczną metodę `stopBrowsingForPeers()` oraz zatrzymuje sesję poprzez wywołanie metody `disconnect()` obiektu `MCSession`. Obie metody zostały przedstawione w algorytmie 3.6.
|
||||
W momencie, gdy autor notatki otworzy ekran edycji, wykonuje się metoda `startServer()`, która wywołuje metodę `startBrowsingForPeers()` obiektu `MCNearbyServiceBrowser`. Opuszczenie ekranu edycji wywołuje metodę `stopServer()`, która wywołuje analogiczną metodę `stopBrowsingForPeers()` oraz zatrzymuje sesję poprzez wywołanie metody `disconnect()` obiektu `MCSession`. Obie metody zostały przedstawione w programie 3.6.
|
||||
|
||||
#let note_editing_session_server_edge_lifecycle = [```swift
|
||||
func startServer() {
|
||||
@@ -148,7 +148,7 @@ func stopServer() {
|
||||
caption: [Implementacja metod odpowiedzialnych za nasłuchiwanie na dostępnych klientów],
|
||||
)
|
||||
|
||||
Obiekt `browser` w momencie wykrycia nowego użytkownika w pobliżu, wywołuje naszą metodę o nazwie `browser()`, która przyjmuje wszystkie potrzebne informacje o znalezionym użytkowniku. Implementacja mojego systemu, jak zostało to przedstawione w algorytmie 3.7, następnie upewnia się czy odkryty użytkownik nie jest jednocześnie autorem notatki, co jest znanym błędem w Multipeer Connectivity. Następnie po udanej weryfikacji dodajemy nowy obiekt dostępnego użytkownika do tablicy na podstawie której jest budowany interfejs z listą dostępnych użytkowników.
|
||||
Obiekt `browser` w momencie wykrycia nowego użytkownika w pobliżu, wywołuje naszą metodę o nazwie `browser()`, która przyjmuje wszystkie potrzebne informacje o znalezionym użytkowniku. Implementacja mojego systemu, jak zostało to przedstawione w programie 3.7, następnie upewnia się czy odkryty użytkownik nie jest jednocześnie autorem notatki, co jest znanym błędem w Multipeer Connectivity. Następnie po udanej weryfikacji dodajemy nowy obiekt dostępnego użytkownika do tablicy na podstawie której jest budowany interfejs z listą dostępnych użytkowników.
|
||||
|
||||
#let note_editing_session_server_browser_found_peer = [```swift
|
||||
func browser(
|
||||
@@ -169,7 +169,7 @@ func browser(
|
||||
caption: [Implementacja metody browser wywoływanej w przypadku wykrycia innego klienta],
|
||||
)
|
||||
|
||||
W sytuacji gdy użytkownik straci połączenie z połączonym klientem, następuje usunięcie jego identyfikatora z listy, poprzez wywołanie metody o takiej samej nazwie, ale z parametrami wskazującymi na scenariusz zgubienia klienta, tak jak zostało to zapisane w algorytmie 3.8.
|
||||
W sytuacji gdy użytkownik straci połączenie z połączonym klientem, następuje usunięcie jego identyfikatora z listy, poprzez wywołanie metody o takiej samej nazwie, ale z parametrami wskazującymi na scenariusz zgubienia klienta, tak jak zostało to zapisane w programie 3.8.
|
||||
|
||||
#let note_editing_session_server_browser_lost_peer = [```swift
|
||||
func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
|
||||
@@ -188,7 +188,7 @@ func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
|
||||
|
||||
Każda modyfikacja obiektów klasy w tym wypadku musi zostać wywołana na tym samym wątku, ponieważ Multipeer Connectivity nie gwarantuje, że kod będzie się wykonał zawsze na tym samym wątku. Wybrałem wątek główny, ze względu na bezpośrednie użycie właściwości klasy wewnątrz obiektów odpowiedzialnych za budowę interfejsu użytkownika.
|
||||
|
||||
Algorytm 3.9 przedstawia część systemu, gdzie o stanie połączenia z innymi klientami system jest informowany przez wykonanie metody `session` z parametrami zawierającymi informację o stanie, który jest reprezentowany przez typ enumeracji, obiekt sesji oraz identyfikator klienta, których ten stan połączenia dotyczy. Na podstawie tych argumentów, aplikacja aktualizuje tablicę `visiblePeers`.
|
||||
Program 3.9 przedstawia część systemu, gdzie o stanie połączenia z innymi klientami system jest informowany przez wykonanie metody `session` z parametrami zawierającymi informację o stanie, który jest reprezentowany przez typ enumeracji, obiekt sesji oraz identyfikator klienta, których ten stan połączenia dotyczy. Na podstawie tych argumentów, aplikacja aktualizuje tablicę `visiblePeers`.
|
||||
|
||||
#let note_editing_session_server_session_peer_did_change_state = [```swift
|
||||
func session(
|
||||
@@ -219,7 +219,7 @@ func session(
|
||||
caption: [Implementacja metody session wywoływanej w przypadku zmiany stanu połączenia z konkretnym klientem],
|
||||
)
|
||||
|
||||
Przechodząc do implementacji klienta, całość jest reprezentowana przez obiekt `NoteEditingSessionClient`. Jego konstruktor, obecny w algorytmie 3.10, przyjmuje tylko identyfikator użytkownika, który jest typem `MCPeerID`, a implementacja obejmuje również stworzenie instancji `MCSession` do wykorzystania w trakcie połączenia z serwerem oraz instancji `MCNearbyServiceAdvertiser`, która propaguje informacje o kliencie do wszystkich innych klientów w pobliżu. Do obu obiektów przypisujemy obiekt delegujący, który będzie właśnie utworzoną instancją `NoteEditingSessionClient`. Dla `MCNearbyServiceAdvertiser` obiekt delegującego musi implementować protokół `MCNearbyServiceAdvertiserDelegate`.
|
||||
Przechodząc do implementacji klienta, całość jest reprezentowana przez obiekt `NoteEditingSessionClient`. Jego konstruktor, obecny w programie 3.10, przyjmuje tylko identyfikator użytkownika, który jest typem `MCPeerID`, a implementacja obejmuje również stworzenie instancji `MCSession` do wykorzystania w trakcie połączenia z serwerem oraz instancji `MCNearbyServiceAdvertiser`, która propaguje informacje o kliencie do wszystkich innych klientów w pobliżu. Do obu obiektów przypisujemy obiekt delegujący, który będzie właśnie utworzoną instancją `NoteEditingSessionClient`. Dla `MCNearbyServiceAdvertiser` obiekt delegującego musi implementować protokół `MCNearbyServiceAdvertiserDelegate`.
|
||||
|
||||
#let note_editing_session_client_init = [```swift
|
||||
init(peer: MCPeerID) {
|
||||
@@ -246,7 +246,7 @@ init(peer: MCPeerID) {
|
||||
caption: [Implementacja konstruktora obiektu NoteEditingSessionClient],
|
||||
)
|
||||
|
||||
Instancja `NoteEditingSessionClient` jest tworzona już przy pierwszym uruchomieniu aplikacji. Po utworzeniu przez użytkownika przyjaznej nazwy, która będzie używana do identyfikacji, w tle wywoływana jest metoda `startBrowsingForNotes()`, która wywołuje `startAdvertisingPeer()` obiektu `advertiser`. Przed rozpoczęciem nasłuchiwania aplikacja wywołuje również `stopBrowsingForNotes()`, by zatrzymać nasłuchiwanie, jeśli wcześniej było ono rozpoczęte, oraz zatrzymuje działanie obiektu `MCSession`, by zamknąć ewentualnie istniejącą sesję edycji notatki. Obie metody zostały przedstawione w algorytmie 3.11.
|
||||
Instancja `NoteEditingSessionClient` jest tworzona już przy pierwszym uruchomieniu aplikacji. Po utworzeniu przez użytkownika przyjaznej nazwy, która będzie używana do identyfikacji, w tle wywoływana jest metoda `startBrowsingForNotes()`, która wywołuje `startAdvertisingPeer()` obiektu `advertiser`. Przed rozpoczęciem nasłuchiwania aplikacja wywołuje również `stopBrowsingForNotes()`, by zatrzymać nasłuchiwanie, jeśli wcześniej było ono rozpoczęte, oraz zatrzymuje działanie obiektu `MCSession`, by zamknąć ewentualnie istniejącą sesję edycji notatki. Obie metody zostały przedstawione w programie 3.11.
|
||||
|
||||
#let note_editing_session_client_edge_lifecycle = [```swift
|
||||
func startBrowsingForNotes() {
|
||||
@@ -265,7 +265,7 @@ func stopBrowsingForNotes() {
|
||||
caption: [Implementacja metod odpowiedzialnych za nasłuchiwanie na dostępne sesje edycji notatek],
|
||||
)
|
||||
|
||||
Algorytm 3.12 przedstawia jedyną metodę wymaganą przez protokół `MCNearbyServiceAdvertiserDelegate` - nazywa się `advertiser()` i w argumentach przyjmuje informację o otrzymanym zaproszeniu i metadanych jakie to zaproszenie zawierało. Aplikacja próbuje zdekodować migawkę notatki, a następnie konstruuje obiekt `NoteInvitation` i umieszcza go w tablicy `invitations`. Umieszczenie w tablicy trzeba wykonać na głównym wątku, ponieważ, analogicznie jak w wypadku implementacji `MCNearbyServiceBrowserDelegate`, nie mamy gwarancji na jakim wątku będzie wykonywała się ta metoda.
|
||||
Program 3.12 przedstawia jedyną metodę wymaganą przez protokół `MCNearbyServiceAdvertiserDelegate` - nazywa się `advertiser()` i w argumentach przyjmuje informację o otrzymanym zaproszeniu i metadanych jakie to zaproszenie zawierało. Aplikacja próbuje zdekodować migawkę notatki, a następnie konstruuje obiekt `NoteInvitation` i umieszcza go w tablicy `invitations`. Umieszczenie w tablicy trzeba wykonać na głównym wątku, ponieważ, analogicznie jak w wypadku implementacji `MCNearbyServiceBrowserDelegate`, nie mamy gwarancji na jakim wątku będzie wykonywała się ta metoda.
|
||||
|
||||
#let note_editing_client_advertiser_did_receive_invitation = [```swift
|
||||
func advertiser(
|
||||
@@ -305,7 +305,7 @@ func advertiser(
|
||||
|
||||
== Transportowanie danych
|
||||
|
||||
W wysyłanym zaproszeniu wysyłamy w metadanych migawkę notatki zawierającą jej tytuł oraz ostatnio dostępną treść. Migawka jest kodowana w formacie JSON bazując na strukturze `NoteContent`. Przykładowym poprawnym zapisem JSON tej struktury jest przykład w algorytmie 3.13.
|
||||
W wysyłanym zaproszeniu wysyłamy w metadanych migawkę notatki zawierającą jej tytuł oraz ostatnio dostępną treść. Migawka jest kodowana w formacie JSON bazując na strukturze `NoteContent`. Przykładowym poprawnym zapisem JSON tej struktury jest przykład w programie 3.13.
|
||||
|
||||
#let note_content_json_representation = [```json
|
||||
{
|
||||
@@ -320,7 +320,7 @@ W wysyłanym zaproszeniu wysyłamy w metadanych migawkę notatki zawierającą j
|
||||
caption: [Reprezentacja obiektu NoteContent w zapisie JSON],
|
||||
)
|
||||
|
||||
Po stronie klienta, każda zmiana jest ogłaszana serwerowi poprzez wywołanie metody `send()` obiektu `NoteEditingSessionClient`, zapisaną w algorytmie 3.14, która przyjmuje identyfikator użytkownika do które ma wiadomość trafić oraz całą zawartość notatki. Jej implementacja zamienia notatkę wraz z identyfikatorem w typ `NoteMessage`, następnie koduje ją do formatu JSON, finalnie próbuje ją wysłać do określonego użytkownika.
|
||||
Po stronie klienta, każda zmiana jest ogłaszana serwerowi poprzez wywołanie metody `send()` obiektu `NoteEditingSessionClient`, zapisaną w programie 3.14, która przyjmuje identyfikator użytkownika do które ma wiadomość trafić oraz całą zawartość notatki. Jej implementacja zamienia notatkę wraz z identyfikatorem w typ `NoteMessage`, następnie koduje ją do formatu JSON, finalnie próbuje ją wysłać do określonego użytkownika.
|
||||
|
||||
#let note_editing_session_client_send_note = [```swift
|
||||
func send(note: String, to peer: MCPeerID) {
|
||||
@@ -336,7 +336,7 @@ func send(note: String, to peer: MCPeerID) {
|
||||
caption: [Implementacja metody send wysyłającej kopię notatki do serwera],
|
||||
)
|
||||
|
||||
Przykładowy zapis instancji obiektu `NoteMessage` wygląda tak jak w algorytmie 3.15:
|
||||
Przykładowy zapis instancji obiektu `NoteMessage` wygląda tak jak w programie 3.15:
|
||||
|
||||
#let note_message_json_representation = [```json
|
||||
{
|
||||
@@ -351,7 +351,7 @@ Przykładowy zapis instancji obiektu `NoteMessage` wygląda tak jak w algorytmie
|
||||
caption: [Reprezentacja obiektu NoteMessage w zapisie JSON],
|
||||
)
|
||||
|
||||
Po tym jak serwer odbierze wysłaną wiadomość, wywoływana jest metoda `session`, widoczna w algorytmie 3.16, która w argumentach przekazuje zakodowane dane, sesję serwera oraz identyfikator użytkownika, który wysłał załączone dane. Po udanym zdekodowaniu danych, wybieramy wszystkich użytkowników, którzy dołączyli do sesji edycji notatki i wysyłamy do nich kopię otrzymanej wiadomości, a serwer dodatkowo wysyła identyczną kopię do warstwy prezentacji.
|
||||
Po tym jak serwer odbierze wysłaną wiadomość, wywoływana jest metoda `session`, widoczna w programie 3.16, która w argumentach przekazuje zakodowane dane, sesję serwera oraz identyfikator użytkownika, który wysłał załączone dane. Po udanym zdekodowaniu danych, wybieramy wszystkich użytkowników, którzy dołączyli do sesji edycji notatki i wysyłamy do nich kopię otrzymanej wiadomości, a serwer dodatkowo wysyła identyczną kopię do warstwy prezentacji.
|
||||
|
||||
|
||||
#let code_session_did_receive_data_server = [```swift
|
||||
@@ -436,7 +436,7 @@ Jeśli aplikacja została dopiero zainstalowana, system pokaże też monit użyt
|
||||
|
||||
== Napotkane wyzwania implementacyjne i rozwiązania
|
||||
|
||||
W czasie implementacji opisywanej aplikacji natknąłem się na problemy, które były związane z poszczególnymi częściami synchronizacji tekstu. Pierwszym z nich jest zachowanie frameworka Multipeer Connectivity, gdzie nasłuchiwanie na dostępnych użytkowników za pomocą `MCNearbyServiceBrowser` wykrywa także samego siebie jako jednego z dostępnych klientów. Skutkowało to wysyłaniem zmian przez urządzenie do samego siebie oraz otrzymywanie zduplikowanych kopii notatek z innych urządzeń. By temu zapobiec, w metodzie obsługującej wykrywanie dostępnych użytkowników - widocznej w algorytmie 3.7 - zaimplementowałem filtr, który ignoruje użytkowników o takiej samej nazwie użytkownika.
|
||||
W czasie implementacji opisywanej aplikacji natknąłem się na problemy, które były związane z poszczególnymi częściami synchronizacji tekstu. Pierwszym z nich jest zachowanie frameworka Multipeer Connectivity, gdzie nasłuchiwanie na dostępnych użytkowników za pomocą `MCNearbyServiceBrowser` wykrywa także samego siebie jako jednego z dostępnych klientów. Skutkowało to wysyłaniem zmian przez urządzenie do samego siebie oraz otrzymywanie zduplikowanych kopii notatek z innych urządzeń. By temu zapobiec, w metodzie obsługującej wykrywanie dostępnych użytkowników - widocznej w programie 3.7 - zaimplementowałem filtr, który ignoruje użytkowników o takiej samej nazwie użytkownika.
|
||||
|
||||
Następnym problemem związanym z Multipeer Connectivity jest jego ogólna niestabilność. W sytuacjach, gdzie połączenie staje się niestabilne - oddalenie się od siebie użytkowników, gdy do komunikacji jest wykorzystywany Bluetooth; przełączanie się między sieciami Wi-Fi - otrzymywane zdarzenia nie są deterministyczne. Czasem aplikacja otrzymywała na przemian informacje o rozłączeniu i ponownym połączeniu się z zaproszonymi klientami, czasem nigdy nie otrzymywała informacji o tym, że klient się rozłączył. Podobne problemy zostały zauważone w momencie wyjścia z aplikacji - wielokrotnie aplikacja traciła połączenie z innymi klientami, ale metody, które powinny zostać z tego powodu wywołane, czasami nie były wykonywane.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Testy jednostkowe zostały wykonane w izolacji od systemu plików oraz framework
|
||||
|
||||
== Testy jednostkowe
|
||||
|
||||
Podstawowym przetestowanym komponentem jest klasa `NotesStorage`, odpowiedzialna za zarządzanie cyklem życia notatek w lokalnym systemie plików. Do testów przygotowałem zastępczą implementację `InMemoryStorageProvider` (algorytm 4.1), która symuluje zachowanie systemu plików w pamięci operacyjnej. Implementacja ta przechowuje pliki w słowniku, udostępniając zawartość katalogu oraz tworzenia plików zgodnie z protokołem `StorageProvider`.
|
||||
Podstawowym przetestowanym komponentem jest klasa `NotesStorage`, odpowiedzialna za zarządzanie cyklem życia notatek w lokalnym systemie plików. Do testów przygotowałem zastępczą implementację `InMemoryStorageProvider` (program 4.1), która symuluje zachowanie systemu plików w pamięci operacyjnej. Implementacja ta przechowuje pliki w słowniku, udostępniając zawartość katalogu oraz tworzenia plików zgodnie z protokołem `StorageProvider`.
|
||||
|
||||
#let in_memory_storage = [```swift
|
||||
final class InMemoryStorageProvider: StorageProvider {
|
||||
@@ -51,7 +51,7 @@ final class InMemoryStorageProvider: StorageProvider {
|
||||
caption: [Implementacja zastępczego StorageProvider dla testów jednostkowych],
|
||||
)
|
||||
|
||||
Na bazie powyższego kodu zbudowałem pięć przypadków testowych klasy `NotesStorageTests` (algorytm 4.2). Pierwszy z nich weryfikuje, że przy pustym katalogu metoda `loadNotes()` zwraca pustą tablicę. Kolejne dwa testy sprawdzają poprawność tworzenia pliku oraz odczytu istniejącej notatki. Ostatni test sprawdza, czy dwukrotne wywołanie metody tworzącej notatkę o tej samej nazwie generuje dwa odrębne obiekty.
|
||||
Na bazie powyższego kodu zbudowałem pięć przypadków testowych klasy `NotesStorageTests` (program 4.2). Pierwszy z nich weryfikuje, że przy pustym katalogu metoda `loadNotes()` zwraca pustą tablicę. Kolejne dwa testy sprawdzają poprawność tworzenia pliku oraz odczytu istniejącej notatki. Ostatni test sprawdza, czy dwukrotne wywołanie metody tworzącej notatkę o tej samej nazwie generuje dwa odrębne obiekty.
|
||||
|
||||
#let notes_storage_tests = [```swift
|
||||
@Suite
|
||||
@@ -122,7 +122,7 @@ struct NotesStorageTests {
|
||||
|
||||
Komunikacja między urządzeniami wymaga poprawnej serializacji i deserializacji obiektów domenowych do formatu JSON. W ramach testów jednostkowych zweryfikowano dwie kluczowe struktury: `NoteMessage` oraz `NoteInvitation.NoteContent`.
|
||||
|
||||
`NoteMessageCodableTests` (algorytm 4.3) zawiera trzy przypadki testowe. Pierwszy sprawdza, czy obiekt jest poprawnie kodowany do formatu JSON z zachowaniem oczekiwanych nazw kluczy (`senderID`, `content`). Drugi weryfikuje poprawność dekodowania z ciągu znaków JSON. Trzeci test polega na zakodowaniu obiektu, potem zdekodowaniu i porównaniu go z obiektem oryginalnym.
|
||||
`NoteMessageCodableTests` (program 4.3) zawiera trzy przypadki testowe. Pierwszy sprawdza, czy obiekt jest poprawnie kodowany do formatu JSON z zachowaniem oczekiwanych nazw kluczy (`senderID`, `content`). Drugi weryfikuje poprawność dekodowania z ciągu znaków JSON. Trzeci test polega na zakodowaniu obiektu, potem zdekodowaniu i porównaniu go z obiektem oryginalnym.
|
||||
|
||||
#let note_message_codable_tests = [```swift
|
||||
@Suite
|
||||
@@ -162,7 +162,7 @@ struct NoteMessageCodableTests {
|
||||
caption: [Testy kodowania i dekodowania NoteMessage],
|
||||
)
|
||||
|
||||
Analogiczny zbiór testów został przygotowany dla struktury `NoteInvitation.NoteContent` (algorytm 4.4), która reprezentuje migawkę notatki przesyłaną w zaproszeniu do sesji.
|
||||
Analogiczny zbiór testów został przygotowany dla struktury `NoteInvitation.NoteContent` (program 4.4), która reprezentuje migawkę notatki przesyłaną w zaproszeniu do sesji.
|
||||
|
||||
#let note_content_codable_tests = [```swift
|
||||
@Suite
|
||||
@@ -193,7 +193,7 @@ struct NoteContentCodableTests {
|
||||
caption: [Testy kodowania i dekodowania NoteContent],
|
||||
)
|
||||
|
||||
`NoteInvitationTests` (algorytm 4.5) obejmuje siedem przypadków testowych. Dwa pierwsze weryfikują, czy metody `accept()` oraz `decline()` przekazują odpowiednio wartości logiczne `true` i `false` do handlera. Kolejne dwa testy sprawdzają czy wielokrotne wywołanie tej samej metody nie powoduje powtórnego wywołania handlera. Piąty test gwarantuje, że po zaakceptowaniu zaproszenia próba jego odrzucenia jest ignorowana. Ostatnie dwa testy weryfikują poprawność obliczanych właściwości: `noteName` zwraca tytuł notatki, a `id` jest tożsame z identyfikatorem nadawcy (`MCPeerID`).
|
||||
`NoteInvitationTests` (program 4.5) obejmuje siedem przypadków testowych. Dwa pierwsze weryfikują, czy metody `accept()` oraz `decline()` przekazują odpowiednio wartości logiczne `true` i `false` do handlera. Kolejne dwa testy sprawdzają czy wielokrotne wywołanie tej samej metody nie powoduje powtórnego wywołania handlera. Piąty test gwarantuje, że po zaakceptowaniu zaproszenia próba jego odrzucenia jest ignorowana. Ostatnie dwa testy weryfikują poprawność obliczanych właściwości: `noteName` zwraca tytuł notatki, a `id` jest tożsame z identyfikatorem nadawcy (`MCPeerID`).
|
||||
|
||||
#let note_invitation_tests = [```swift
|
||||
@Suite
|
||||
|
||||
LFS
BIN
Binary file not shown.
+1
-1
@@ -303,7 +303,7 @@
|
||||
it.body
|
||||
v(10pt, weak: true)
|
||||
align(center, block(width: auto)[
|
||||
#text(weight: "bold", fill: blue-zut, font: "Fira Sans")[Algorytm #full-number:]
|
||||
#text(weight: "bold", fill: blue-zut, font: "Fira Sans")[Program #full-number:]
|
||||
#text(font: "Fira Sans")[#it.caption.body]
|
||||
])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user