Najlepsze podejścia architektoniczne do budowania aplikacji sieciowych iOS (klienci REST)

Jestem programistą iOS z pewnym doświadczeniem i to pytanie jest dla mnie naprawdę interesujące. Widziałem wiele różnych zasobów i materiałów na ten temat, ale mimo to nadal jestem zdezorientowany. Jaka jest najlepsza Architektura aplikacji sieciowej na iOS? Chodzi mi o podstawowe abstrakcyjne frameworki, wzorce, które pasują do każdej aplikacji sieciowej, czy to małej aplikacji, która ma tylko kilka żądań serwera, czy złożonego klienta REST. Apple zaleca użycie MVC jako podstawowej architektury podejście do wszystkich aplikacji iOS, ale ani MVC, ani bardziej nowoczesne wzorce MVVM nie wyjaśniają, gdzie umieścić kod logiki sieci i jak go zorganizować w ogóle.

Czy muszę rozwijać coś takiego MVCS(S dla Service) i w tej warstwie Service umieścić wszystkie API żądania i inne logiki sieciowe, które w perspektywie mogą być naprawdę złożone? Po przeprowadzeniu pewnych badań znalazłem dwa podstawowe podejścia do tego. tutaj zalecono utworzenie osobnej klasy dla każdego network request to web-service API (Jak LoginRequest class lub PostCommentRequest class i tak dalej), które wszystkie dziedziczą z podstawowego żądania abstract class AbstractBaseRequest i oprócz tworzenia jakiegoś globalnego menedżera sieci, który zawiera wspólny kod sieciowy i inne preferencje (może to być AFNetworking customization lub RestKit tuning, jeśli mamy złożone mapowania obiektów i trwałość, lub nawet własną implementację komunikacji sieciowej ze standardowym API). Ale to podejście wydaje mi się narzutą. Inny podejście ma mieć klasę singleton API dispatcher lub manager, jak w pierwszym podejściu, , ale nie do tworzenia klas dla każdego żądania i zamiast tego do hermetyzacji każdego żądania jako instancji publicznej metody tej klasy menedżera, jak: fetchContacts, loginUser metody itp. Więc, jaki jest najlepszy i poprawny sposób? Czy są inne ciekawe podejścia, których jeszcze nie znam?

I czy powinienem utworzyć kolejną warstwę dla tych wszystkich rzeczy sieciowych, takich jak Service, czy NetworkProvider warstwa lub cokolwiek na górze z mojej Architektury MVC, czy ta warstwa powinna być zintegrowana (wtryskiwana) do istniejących MVC warstw np. Model?

Wiem, że istnieje piękne podejście, albo jak takie mobilne potwory, jak klient Facebook lub klient LinkedIn, radzą sobie z wykładniczo rosnącą złożonością logiki sieciowej?

Wiem, że nie ma dokładnej i formalnej odpowiedzi na problem. Celem tego pytania jest zebranie najciekawszych podejść od doświadczonych programistów iOS. Na najlepiej sugerowane podejście zostanie oznaczone jako zaakceptowane i nagrodzone nagrodą za reputację, a inne zostaną nagrodzone. Jest to głównie pytanie teoretyczne i badawcze. Chcę zrozumieć podstawowe, abstrakcyjne i poprawne podejście architektoniczne do aplikacji sieciowych w iOS. Mam nadzieję na szczegółowe wyjaśnienia od doświadczonych programistów.

Author: Community, 2014-06-11

11 answers

I want to understand basic, abstract and correct architectural approach for networking applications in iOS : nie ma "najlepszego" lub "najbardziej poprawnego" podejścia do budowania architektury aplikacji. Jest to bardzo twórcza praca. Zawsze powinieneś wybrać najprostszą i najbardziej rozszerzalną architekturę, która będzie jasna dla każdego dewelopera, który zacznie pracować nad Twoim projektem lub dla innych programistów w Twoim zespole, ale Zgadzam się, że może być "dobra" i "zła" Architektura.

Powiedziałeś: collect the most interesting approaches from experienced iOS developers, nie sądzę, że moje podejście jest najbardziej ciekawe lub poprawne, ale użyłem go w kilku projektach i jestem z niego zadowolony. Jest to hybrydowe podejście tych, o których wspomniałeś powyżej, a także z ulepszeniami z moich własnych wysiłków badawczych. Interesują mnie problemy podejść budowlanych, które łączą w sobie kilka znanych wzorców i idiomów. Myślę, że wiele wzorców korporacyjnych Fowlera można z powodzeniem zastosować do aplikacji mobilnych. Oto lista najciekawszych, o które możemy się ubiegać tworzenie architektury aplikacji iOS (moim zdaniem): warstwa usług, jednostka pracy, Zdalna Elewacja, obiekt transmisji danych, Gateway, Supertype warstwy, przypadek szczególny, model domeny . Należy zawsze prawidłowo zaprojektować warstwę modelu i zawsze nie zapominać o trwałości (może to znacznie zwiększyć wydajność aplikacji). Możesz użyć Core Data do tego. Ale ty powinieneś nie zapomnij, że Core Data nie jest ORM ani bazą danych, ale menedżerem grafów obiektowych z trwałością jako dobrą opcją. Dlatego bardzo często Core Data może być zbyt ciężki dla Twoich potrzeb i możesz przyjrzeć się nowym rozwiązaniom, takim jak Realm i Couchbase Lite, lub zbudować własną lekką warstwę mapowania obiektów/trwałości, opartą na surowych SQLite lub LevelDB {92]}. Radzę również zapoznać się z Domain Driven Design i CQRS .

Na początku myślę, że powinniśmy stworzyć kolejną warstwę dla sieci, ponieważ nie chcemy kontrolerów fat ani ciężkich, przytłoczonych modeli. Nie wierzę w te rzeczy. Ale ja wierzę W podejście skinny everything, ponieważ żadna klasa nie powinna być gruba, nigdy. Cała sieć może być ogólnie abstrakcyjna jako logika biznesowa, w związku z tym powinniśmy mieć inną warstwę, w której możemy ją umieścić. warstwa serwisowa jest tym, co my potrzeba:

It encapsulates the application's business logic,  controlling transactions 
and coordinating responses in the implementation of its operations.

W naszym MVC świecie Service Layer jest coś w rodzaju pośrednika między modelem domeny a kontrolerami. Istnieje dość podobna odmiana tego podejścia o nazwie MVCS gdzie Store jest w rzeczywistości naszą Service warstwą. Store vends model instancji i obsługuje sieci, buforowanie itp. Chcę wspomnieć, że nie powinieneś pisać całej swojej sieci i logiki biznesowej w warstwie usług. To również można uznać za zły projekt. Więcej info spójrz na anemiczne ibogate modele domen. Niektóre metody usług i logika biznesowa mogą być obsługiwane w modelu, więc będzie to model" bogaty " (z zachowaniem).

Zawsze intensywnie korzystam z dwóch bibliotek: AFNetworking 2.0 i ReactiveCocoa. Myślę, że jest to musi mieć dla każdej nowoczesnej aplikacji, która współdziała z siecią i usługami sieciowymi lub zawiera złożoną logikę interfejsu użytkownika.

Architektura

Na początku tworzę klasę general APIClient, która jest podklasą AFHTTPSessionManager . Jest to koń pociągowy wszystkich sieci w aplikacji: wszystkie klasy usług delegują do niej rzeczywiste żądania odpoczynku. Zawiera wszystkie dostosowania klienta HTTP, których potrzebuję w konkretnej aplikacji: przypinanie SSL, przetwarzanie błędów i tworzenie prostych obiektów NSError ze szczegółowymi przyczynami awarii i opisami wszystkich API i błędy połączenia (w takim przypadku kontroler będzie mógł wyświetlać poprawne wiadomości dla użytkownika), ustawianie serializerów żądań i odpowiedzi, nagłówków http i innych rzeczy związanych z siecią. Następnie logicznie podzielę wszystkie żądania API na podusługi lub, bardziej poprawnie, mikroserwisy : UserSerivces, CommonServices, SecurityServices, FriendsServices i tak dalej, zgodnie z logiką biznesową, którą wdrażają. Każda z tych mikrousług jest osobną klasą. Razem tworzą Service Layer. Klasy te zawierają metody dla każdego żądania API przetwarzają modele domeny i zawsze zwracają RACSignal z parsowanym modelem odpowiedzi lub NSError do wywołującego.

Chcę wspomnieć, że jeśli masz złożoną logikę serializacji modeli-utwórz dla niej kolejną warstwę: coś w stylu Data Mapper , ale bardziej ogólnego np. JSON/XML -> Model mapper. Jeśli masz bufor: utwórz go jako oddzielną warstwę/usługę (nie należy mieszać logiki biznesowej z buforowaniem). Dlaczego? Ponieważ poprawna warstwa buforowania może być dość złożony z własnymi gotchas. Ludzie implementują złożoną logikę, aby uzyskać poprawne, przewidywalne buforowanie, takie jak np. buforowanie monoidalne z projekcjami opartymi na profunctorach. Możesz przeczytać o tej pięknej bibliotece o nazwie Carlos aby zrozumieć więcej. I nie zapominaj, że podstawowe dane mogą naprawdę pomóc we wszystkich problemach z buforowaniem i pozwolą Ci pisać mniej logiki. Ponadto, jeśli masz jakąś logikę pomiędzy NSManagedObjectContext A modelami żądań serwera, możesz użyć wzorca repozytorium , który oddziela logikę, która pobiera dane i mapuje je do modelu jednostki, od logiki biznesowej, która działa na modelu. Radzę więc używać wzorca repozytorium, nawet jeśli masz podstawową architekturę opartą na danych. Repozytorium może abstrakcyjne rzeczy, takie jak NSFetchRequest,NSEntityDescription, NSPredicate i tak dalej do prostych metod, takich jak get lub put.

Po tych wszystkich czynnościach w warstwie usług, wywołujący (kontroler widoku) może wykonać kilka skomplikowanych czynności asynchronicznych z odpowiedzią: manipulacje sygnałem, łańcuchowanie, mapowanie itp. z pomocą ReactiveCocoa prymitywów , lub po prostu subskrybuj go i pokaż wyniki w widoku. Wykonuję iniekcję Dependency Injection we wszystkich tych klasach usług my APIClient, co przełoży konkretne wywołanie usługi na odpowiednie GET, POST, PUT, DELETE, itd. prośba do reszty. W tym przypadku {[15] } jest przekazywane domyślnie do wszystkich kontrolerów, można to uczynić jawnie parametryzując klasy usług APIClient. To może mieć sens, jeśli chcesz aby używać różnych dostosowań APIClient dla poszczególnych klas usług, ale jeśli z jakichś powodów nie chcesz dodatkowych kopii lub jesteś pewien, że zawsze będziesz używać jednej konkretnej instancji (bez dostosowywania) APIClient - zrób z niej singleton, ale nie rób, proszę, nie rób klas usług jako singletonów.

Następnie każdy kontroler widoku ponownie z DI wstrzykuje potrzebną klasę usług, wywołuje odpowiednie metody usług i komponuje ich wyniki z logiką interfejsu użytkownika. Na dependency injection lubię używać BloodMagic lub mocniejszego frameworka Typhoon . Nigdy nie używam singletonów, Boga klasy czy innych złych rzeczy. Ponieważ jeśli zadzwonisz do swojej klasy WhateverManager, oznacza to, że nie znasz jej celu i jest to zły wybór projektu. Singletons jest również anty-wzorzec, a w większość przypadków (z wyjątkiem rzadkich) jest niewłaściwym rozwiązaniem. Singleton należy rozważyć tylko wtedy, gdy wszystkie trzy z następujących kryteriów są zadowoleni:

    Prawo własności pojedynczej instancji nie może być w sposób racjonalny przypisane;]}
  1. leniwa inicjalizacja jest pożądana;
  2. globalny dostęp nie jest przewidziany w inny sposób.

W naszym przypadku posiadanie pojedynczej instancji nie jest problemem, a także nie potrzebujemy globalnego dostępu po podzieleniu naszego god Managera na usługi, ponieważ teraz tylko jeden lub kilka dedykowanych kontrolerów potrzebuje konkretnej usługi (np. on).

Powinniśmy zawsze przestrzegać S zasady w SOLID i używać separacji obaw , więc nie umieszczaj wszystkich metod usług i wywołań sieciowych w jednej klasie, ponieważ to szalone, zwłaszcza jeśli tworzysz dużą aplikację korporacyjną. Dlatego powinniśmy rozważyć dependency injection i podejście do usług. Uważam to podejście za nowoczesne i post-OO . W tym przypadku podzieliliśmy naszą aplikację na dwie części: logikę sterowania (kontrolery i zdarzenia) i parametry.

Jednym z parametrów byłyby zwykłe parametry "danych". To właśnie przekazujemy funkcje, manipulujemy, modyfikujemy, persist itp. Są to byty, Agregaty, zbiory, klasy przypadków. Innym rodzajem byłyby parametry "usługi". Są to klasy, które hermetyzują logikę biznesową, umożliwiają komunikację z systemami zewnętrznymi, zapewniają dostęp do danych.

Oto ogólny przepływ pracy mojej architektury na przykładzie. Let ' s Załóżmy, że mamy FriendsViewController, który wyświetla listę znajomych użytkownika i mamy opcję usunięcia ze znajomych. Tworzę metodę w mojej klasie FriendsServices o nazwie:

- (RACSignal *)removeFriend:(Friend * const)friend

Gdzie Friend jest obiektem modelu / domeny (lub może być tylko obiektem User, jeśli mają podobne atrybuty). Poniżej metoda ta parsuje Friend do NSDictionary parametrów JSON friend_id, name, surname, friend_request_id i tak dalej. Zawsze używam Mantle biblioteki dla tego rodzaju kotła i dla mojej warstwy modelu (parsowanie wstecz i do przodu, zarządzanie zagnieżdżonymi hierarchiami obiektów w JSON i tak dalej). Po przetworzeniu wywołań APIClient DELETE metoda wykonania rzeczywistego żądania REST i zwraca Response w RACSignal do wywołującego (FriendsViewController w naszym przypadku), aby wyświetlić odpowiednią wiadomość dla użytkownika lub cokolwiek innego.

Jeśli nasza aplikacja jest bardzo duża, musimy jeszcze wyraźniej oddzielić naszą logikę. Np. nie jest zawsze dobrze mieszać Repository lub modelować logikę z Service jednym. Kiedy opisałem moje podejście, powiedziałem, że removeFriend metoda powinna być w warstwie Service, ale jeśli będziemy bardziej pedantyczni możemy zauważyć, że lepiej należy do Repository. Pamiętajmy, czym jest repozytorium. Eric Evans podał jej dokładny opis w swojej książce [DDD]:

Repozytorium reprezentuje wszystkie obiekty określonego typu jako zbiór pojęciowy. Działa jak zbiór, z wyjątkiem bardziej rozbudowanych możliwości zapytań.

Więc {[61] } jest zasadniczo fasadą, która wykorzystuje semantykę stylu kolekcji (dodaj, Update, Usuń), aby zapewnić dostęp do danych / obiektów. Dlatego gdy masz coś takiego: getFriendsList, getUserGroups, removeFriend możesz umieścić go w Repository, ponieważ semantyka podobna do kolekcji jest tutaj dość jasna. I Kod jak:

- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;

Jest zdecydowanie logiką biznesową, ponieważ wykracza poza podstawowe operacje CRUD i łączy dwa obiekty domeny (Friend i Request), dlatego powinien być umieszczony w warstwie Service. Chcę również zauważyć: nie twórz niepotrzebnych abstrakcji. Użyj wszystkich te podejścia mądrze. Ponieważ jeśli przytłoczysz swoją aplikację abstrakcjami, zwiększy to jej przypadkową złożoność, a złożoność {232]}spowoduje więcej problemów {92]} w systemach oprogramowania niż cokolwiek innego

Opisuję Ci "Stary" przykład Objective-C, ale to podejście można bardzo łatwo dostosować do języka Swift z dużo większą ilością ulepszeń, ponieważ ma więcej przydatnych funkcji i cukru funkcjonalnego. Gorąco polecam korzystanie z tej biblioteki: Moya . Pozwala na stworzenie bardziej eleganckiej warstwy APIClient (nasz koń pociągowy, jak pamiętasz). Teraz nasz APIClient provider będzie typem wartości (enum) z rozszerzeniami zgodnymi z protokołami i wykorzystującymi niszczenie dopasowania wzorców. Swift enums + pattern matching pozwala nam tworzyć algebraiczne typy danych jak w klasycznym programowaniu funkcyjnym. Nasze mikroserwisy będą korzystać z tego ulepszonego dostawcy APIClient, tak jak w zwykłym podejściu Objective-C. Dla warstwy modelu zamiast Mantle można użyj Biblioteki ObjectMapper lub lubię używać bardziej eleganckiej i funkcjonalnej biblioteki Argo.

Tak więc, opisałem moje ogólne podejście architektoniczne, które można dostosować do każdego zastosowania, myślę. Oczywiście może być o wiele więcej ulepszeń. Radzę Ci nauczyć się programowania funkcyjnego, ponieważ możesz z niego wiele skorzystać, ale nie posuwaj się za daleko. Eliminując nadmierny, współdzielony, globalny mutowalny stan, tworząc niezmienny model domeny lub tworzenie czystych funkcji bez zewnętrznych skutków ubocznych jest ogólnie dobrą praktyką i zachęca do tego nowy język {79]}. Ale zawsze pamiętaj, że przeciążenie Twojego kodu ciężkimi, czystymi wzorcami funkcjonalnymi, podejściami kategorii-teoretycznymi jest złym pomysłem, ponieważ inni programiści będą czytać i wspierać Twój kod, i mogą być sfrustrowani lub przerażeni z powodu {80]} i tego typu rzeczy w Twoim niezmiennym modelu. To samo z ReactiveCocoa: nie RACify Twój kod zbyt dużo , ponieważ może stać się nieczytelny naprawdę szybko, szczególnie dla początkujących. Używaj go, gdy może naprawdę uprościć Twoje cele i logikę.

Więc, read a lot, mix, experiment, and try to pick up the best from different architectural approaches. To najlepsza rada, jaką mogę ci dać.

 308
Author: Oleksandr Karaberov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-12 07:31:22

Zgodnie z celem tego pytania, chciałbym opisać nasze podejście do architektury.

Podejście do architektury

Nasza ogólna Architektura aplikacji iOS opiera się na następujących wzorach: warstwy usług, MVVM, powiązanie danych interfejsu użytkownika, Dependency Injection; and Functional Reactive Programming paradygmat.

Możemy pokroić typową aplikację konsumencką w następujące logiczne warstwy:

  • montaż
  • Model
  • usługi
  • Przechowywanie
  • menedżerowie
  • Koordynatorzy
  • UI
  • Infrastruktura

Warstwa montażowa jest punktem bootstrap naszej aplikacji. Zawiera kontener iniekcji zależności oraz deklaracje obiektów aplikacji i ich zależności. Ta warstwa może również zawierać konfigurację aplikacji (adresy URL, klucze usług innych firm itp.). W tym celu korzystamy z biblioteki Typhoon .

Warstwa Modelu zawiera klasy modeli domenowych, walidacje, mapowania. Używamy biblioteki Mantle do mapowania naszych modeli: obsługuje serializację/deserializację do formatu JSON i modeli NSManagedObject. Do walidacji i reprezentacji formularzy naszych modeli używamy bibliotek FXFormsi fxmodelvalidation.

Warstwa usług deklaruje usługi, których używamy do interakcji z zewnętrznymi systemami w aby wysyłać lub odbierać dane, które są reprezentowane w naszym modelu domeny. Zazwyczaj mamy więc usługi do komunikacji z API serwera( na jednostkę), usługi wiadomości (jak PubNub ), Usługi pamięci masowej (jak Amazon S3) itp. Zasadniczo usługi zawijają obiekty dostarczane przez zestawy SDK (na przykład PubNub SDK) lub implementują własną logikę komunikacji. Do ogólnych zastosowań sieciowych używamy biblioteki AFNetworking.

Celem warstwy Storage layer jest organizacja lokalnego przechowywania danych na urządzeniu. Używamy do tego podstawowych danych lub Realm (oba mają plusy i minusy, decyzja o tym, czego użyć, opiera się na konkretnych specyfikacjach). Do konfiguracji podstawowych danych używamy biblioteki MDMCoreData oraz kilku klas - storage - (podobnych do usług), które zapewniają dostęp do lokalnego magazynu dla każdej jednostki. Dla Realm używamy tylko podobnych magazynów, aby mieć dostęp do lokalnej pamięci masowej.

Managers layer to miejsce, w którym żyją nasze abstrakcje/wrappery.

W Menedżerze rola może być:

  • Menedżer poświadczeń z różnymi implementacjami (keychain, NSDefaults, ...)
  • Menedżer bieżącej sesji, który wie, jak zachować i zapewnić bieżącą sesję użytkownika
  • Przechwytywanie rurociągu, który zapewnia dostęp do urządzeń multimedialnych (nagrywanie wideo, audio, robienie zdjęć)
  • Ble Manager, który zapewnia dostęp do usług bluetooth i urządzeń peryferyjnych [24]}
  • Geo Location Manager
  • ...

Więc, w roli menedżera może to być dowolny obiekt, który implementuje logikę określonego aspektu lub troski potrzebnej do działania aplikacji.

Staramy się unikać singletonów, ale ta warstwa jest miejscem, w którym żyją, jeśli są potrzebne.

Coordinators layer dostarcza obiekty, które zależą od obiektów z innych warstw (Service, Storage, Model) w celu połączenia ich logiki w jedną sekwencję pracy potrzebną dla określonego modułu (feature, screen, user story lub user experience). Zwykle łańcuchy operacje asynchroniczne i wie, jak reagować na ich przypadki sukcesu i porażki. Jako przykład można sobie wyobrazić funkcję przesyłania wiadomości i odpowiadający jej obiekt MessagingCoordinator. Obsługa operacji wysyłania wiadomości może wyglądać tak:

  1. Validate message (model layer)
  2. Zapisz wiadomość lokalnie (przechowywanie wiadomości)
  3. załącznik do przesłania wiadomości (usługa amazon S3)
  4. zaktualizuj status wiadomości i adresy URL załączników i zapisz wiadomość lokalnie (przechowywanie wiadomości)
  5. Serializowanie Wiadomości do formatu JSON (warstwa modelu)
  6. opublikuj wiadomość do PubNub (usługa PubNub)
  7. zaktualizuj status i atrybuty wiadomości i zapisz je lokalnie (przechowywanie wiadomości)

Na każdym z powyższych kroków błąd jest odpowiednio obsługiwany.

Warstwa interfejsu składa się z następujących podwarstw:

  1. ViewModels
  2. ViewControllers
  3. Views

W celu uniknięcia ogromnych kontrolerów widoku używamy Wzorzec MVVM i implementacja logiki potrzebne do prezentacji UI w ViewModels. ViewModel zazwyczaj ma koordynatorów i menedżerów jako zależności. ViewModels używane przez Viewcontrollery i niektóre rodzaje widoków (np. komórki widoku tabeli). Klejem pomiędzy kontrolerami ViewControllers i ViewModels jest powiązanie danych i wzorzec poleceń. Aby możliwe było posiadanie tego kleju używamy biblioteki ReactiveCocoa .

Używamy również ReactiveCocoa i jego koncepcji RACSignal jako interfejsu i zwracającej wartość rodzaj wszystkich koordynatorów, usługi, metody przechowywania. Pozwala nam to na łańcuchowe operacje, uruchamianie ich równolegle lub szeregowo oraz wiele innych przydatnych rzeczy dostarczanych przez ReactiveCocoa.

Staramy się zaimplementować nasze zachowanie UI w deklaratywny sposób. Powiązanie danych i układ Automatyczny bardzo pomagają w osiągnięciu tego celu.

Warstwa Infrastructure zawiera wszystkie helpery, rozszerzenia, narzędzia potrzebne do pracy aplikacji.


To podejście sprawdza się dobrze dla nas i tych typy aplikacji, które zwykle tworzymy. Ale powinieneś zrozumieć, że jest to tylko subiektywne podejście, które powinno być dostosowane/zmienione do konkretnych celów zespołu.

Mam nadzieję, że to ci pomoże!

Więcej informacji na temat procesu tworzenia iOS można znaleźć w tym wpisie na blogu iOS Development as a Service
 29
Author: Alex Petropavlovsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-15 07:45:11

Ponieważ wszystkie aplikacje na iOS są różne, myślę, że istnieją różne podejścia tutaj do rozważenia, ale zwykle idę w ten sposób:
Utwórz klasę Central manager (singleton) do obsługi wszystkich żądań API (Zwykle o nazwie APICommunicator), a każda metoda instancji jest wywołaniem API. I jest jedna centralna (Niepubliczna) metoda:

-(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;

Dla przypomnienia, używam 2 głównych bibliotek / frameworków, ReactiveCocoa i AFNetworking. ReactiveCocoa obsługuje sieci asynchroniczne odpowiedzi doskonale, można zrobić (sendNext:, sendError:, itp.).
Metoda ta wywołuje API, pobiera wyniki i wysyła je przez RAC w formacie ' raw ' (jak nsArray co zwraca AFNetworking).
Następnie metoda jak getStuffList:, która wywołała powyższą metodę subskrybuje jego sygnał, przetwarza surowe dane do obiektów (z czymś w rodzaju Motis) i wysyła obiekty jeden po drugim do wywołującego (getStuffList: i podobne metody również zwracają sygnał, który kontroler może subskrybować).
Na subskrybowany kontroler odbiera obiekty za pomocą bloku subscribeNext: i obsługuje je.

Próbowałem na wiele sposobów w różnych aplikacjach, ale ten działał najlepiej ze wszystkich, więc używam tego w kilku aplikacjach ostatnio, pasuje zarówno do małych, jak i dużych projektów i jest łatwy do rozszerzenia i utrzymania, jeśli coś wymaga modyfikacji.
Mam nadzieję, że to pomoże, chciałbym usłyszeć opinie innych o moim podejściu i być może, jak inni myślą, że można to poprawić.

 17
Author: Rickye,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-11 12:32:06

W mojej sytuacji zwykle używam biblioteki ResKit do konfiguracji warstwy sieciowej. Zapewnia łatwe w użyciu parsowanie. To zmniejsza mój wysiłek na ustawienie mapowania dla różnych odpowiedzi i rzeczy.

Dodaję tylko trochę kodu do ustawienia mapowania automatycznie. Definiuję klasę bazową dla moich modeli (nie protokół z powodu dużej ilości kodu do sprawdzenia czy jakaś metoda jest zaimplementowana czy nie, a mniej kodu w samych modelach):

MappableEntry.h

@interface MappableEntity : NSObject

+ (NSArray*)pathPatterns;
+ (NSArray*)keyPathes;
+ (NSArray*)fieldsArrayForMapping;
+ (NSDictionary*)fieldsDictionaryForMapping;
+ (NSArray*)relationships;

@end

MappableEntry.m

@implementation MappableEntity

+(NSArray*)pathPatterns {
    return @[];
}

+(NSArray*)keyPathes {
    return nil;
}

+(NSArray*)fieldsArrayForMapping {
    return @[];
}

+(NSDictionary*)fieldsDictionaryForMapping {
    return @{};
}

+(NSArray*)relationships {
    return @[];
}

@end

Relacje to obiekty reprezentujące zagnieżdżone obiekty w odpowiedzi:

RelationshipObject.h

@interface RelationshipObject : NSObject

@property (nonatomic,copy) NSString* source;
@property (nonatomic,copy) NSString* destination;
@property (nonatomic) Class mappingClass;

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass;
+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass;

@end

RelationshipObject.m

@implementation RelationshipObject

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = key;
    object.destination = key;
    object.mappingClass = mappingClass;
    return object;
}

+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = source;
    object.destination = destination;
    object.mappingClass = mappingClass;
    return object;
}

@end

Potem ustawiam mapowanie dla Restkitu tak:

ObjectMappingInitializer.h

@interface ObjectMappingInitializer : NSObject

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager;

@end

ObjectMappingInitializer.m

@interface ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses;

@end

@implementation ObjectMappingInitializer

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager {

    NSMutableDictionary *mappingObjects = [NSMutableDictionary dictionary];

    // Creating mappings for classes
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *newMapping = [RKObjectMapping mappingForClass:mappableClass];
        [newMapping addAttributeMappingsFromArray:[mappableClass fieldsArrayForMapping]];
        [newMapping addAttributeMappingsFromDictionary:[mappableClass fieldsDictionaryForMapping]];
        [mappingObjects setObject:newMapping forKey:[mappableClass description]];
    }

    // Creating relations for mappings
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *mapping = [mappingObjects objectForKey:[mappableClass description]];
        for (RelationshipObject *relation in [mappableClass relationships]) {
            [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:relation.source toKeyPath:relation.destination withMapping:[mappingObjects objectForKey:[relation.mappingClass description]]]];
        }
    }

    // Creating response descriptors with mappings
    for (Class mappableClass in [self mappableClasses]) {
        for (NSString* pathPattern in [mappableClass pathPatterns]) {
            if ([mappableClass keyPathes]) {
                for (NSString* keyPath in [mappableClass keyPathes]) {
                    [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:keyPath statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
                }
            } else {
                [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
            }
        }
    }

    // Error Mapping
    RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
    [errorMapping addAttributeMappingsFromArray:[Error fieldsArrayForMapping]];
    for (NSString *pathPattern in Error.pathPatterns) {
        [[RKObjectManager sharedManager] addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)]];
    }
}

@end

@implementation ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses {
    return @[
        [FruiosPaginationResults class],
        [FruioItem class],
        [Pagination class],
        [ContactInfo class],
        [Credentials class],
        [User class]
    ];
}

@end

Jakiś przykład implementacja MappableEntry:

użytkownik.h

@interface User : MappableEntity

@property (nonatomic) long userId;
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, copy) NSString *token;

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password;

- (NSDictionary*)registrationData;

@end

użytkownik.m

@implementation User

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password {
    if (self = [super init]) {
        self.username = username;
        self.email = email;
        self.password = password;
    }
    return self;
}

- (NSDictionary*)registrationData {
    return @{
        @"username": self.username,
        @"email": self.email,
        @"password": self.password
    };
}

+ (NSArray*)pathPatterns {
    return @[
        [NSString stringWithFormat:@"/api/%@/users/register", APIVersionString],
        [NSString stringWithFormat:@"/api/%@/users/login", APIVersionString]
    ];
}

+ (NSArray*)fieldsArrayForMapping {
    return @[ @"username", @"email", @"password", @"token" ];
}

+ (NSDictionary*)fieldsDictionaryForMapping {
    return @{ @"id": @"userId" };
}

@end

Teraz o żądaniach:

Mam plik nagłówkowy z definicją bloków, aby zmniejszyć długość linii we wszystkich klasach APIRequest:

APICallbacks.h

typedef void(^SuccessCallback)();
typedef void(^SuccessCallbackWithObjects)(NSArray *objects);
typedef void(^ErrorCallback)(NSError *error);
typedef void(^ProgressBlock)(float progress);

I przykład mojej klasy APIRequest, której używam:

LoginAPI.h

@interface LoginAPI : NSObject

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError;

@end

LoginAPI.m

@implementation LoginAPI

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError {
    [[RKObjectManager sharedManager] postObject:nil path:[NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] parameters:[credentials credentialsData] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        onSuccess(mappingResult.array);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        onError(error);
    }];
}

@end

And all you need aby zrobić to w kodzie, po prostu zainicjuj obiekt API i wywołaj go, gdy potrzebujesz:

SomeViewController.m

@implementation SomeViewController {
    LoginAPI *_loginAPI;
    // ...
}

- (void)viewDidLoad {
    [super viewDidLoad];

    _loginAPI = [[LoginAPI alloc] init];
    // ...
}

// ...

- (IBAction)signIn:(id)sender {
    [_loginAPI loginWithCredentials:_credentials onSuccess:^(NSArray *objects) {
        // Success Block
    } onError:^(NSError *error) {
        // Error Block
    }];
}

// ...

@end

Mój kod nie jest idealny, ale jest łatwy do jednorazowego ustawienia i użycia dla różnych projektów. Jeśli jest to dla kogoś interesujące, mb mógłbym spędzić trochę czasu i zrobić dla niego uniwersalne rozwiązanie gdzieś na Githubie i CocoaPods.

 7
Author: Andrew Cherkashyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-23 12:14:20

Według mnie cała architektura oprogramowania jest napędzana potrzebami. Jeśli jest to do celów edukacyjnych lub osobistych, a następnie zdecydować główny cel i mieć, że napęd architektury. Jeśli jest to praca na wynajem, to potrzeba biznesowa jest najważniejsza. Sztuczka polega na tym, żeby błyszczące rzeczy nie odciągały Cię od prawdziwych potrzeb. Trudno mi to zrobić. W tym biznesie zawsze pojawiają się nowe błyszczące rzeczy i wiele z nich nie jest przydatnych, ale nie zawsze można to powiedzieć z góry. Skupić się na potrzebie i być gotów porzucić złe wybory, jeśli możesz.

Na przykład niedawno zrobiłem szybki prototyp aplikacji do udostępniania zdjęć dla lokalnej firmy. Ponieważ potrzeba biznesowa polegała na szybkim i brudnym zrobieniu czegoś, Architektura skończyła się kodem iOS, aby wyskoczyć z kamery i trochę kodu sieciowego dołączonego do przycisku Wyślij, który przesłał obraz do sklepu S3 i napisał do domeny SimpleDB. Kod był banalny i koszt minimalny, a Klient ma skalowalną kolekcję zdjęć dostępną przez www z telefonami REST. Tanie i głupie, aplikacja miała wiele wad i zablokować interfejs użytkownika na czas, ale byłoby stratą zrobić więcej dla prototypu i pozwala im wdrożyć do swoich pracowników i generować tysiące obrazów testowych łatwo bez problemów z wydajnością lub skalowalnością. Gówniana architektura, ale idealnie pasuje do potrzeb i kosztów.

Kolejny projekt polegał na wdrożeniu lokalnej bezpiecznej bazy danych, która synchronizuje się z systemem firmy w tle, gdy sieć jest dostępny. Stworzyłem synchronizator tła, który używał Restkitu, ponieważ wydawał się mieć wszystko, czego potrzebowałem. Ale musiałem napisać tyle niestandardowego kodu dla RestKit, aby poradzić sobie z idiosynkratycznym JSON, że mogłem zrobić to szybciej, pisząc własne JSON do transformacji CoreData. Jednak klient chciał wprowadzić tę aplikację do domu i czułem, że RestKit będzie podobny do frameworków, które używali na innych platformach. Czekam, czy to była dobra decyzja.

Znowu dla mnie problemem jest skupienie się na potrzebie i niech to determinuje architekturę. Staram się jak cholera, aby uniknąć korzystania z pakietów innych firm, ponieważ przynoszą koszty, które pojawiają się dopiero po aplikacji jest w polu na chwilę. Staram się unikać tworzenia hierarchii klasowych, ponieważ rzadko się opłacają. Jeśli mogę napisać coś w rozsądnym czasie, zamiast przyjąć pakiet, który nie pasuje idealnie, to robię to. Mój kod jest dobrze skonstruowany do debugowania i odpowiednio skomentowany, ale strona trzecia Pakiety rzadko są. Mając to na uwadze, uważam Af Networking za zbyt przydatny do ignorowania i dobrze zorganizowany, dobrze skomentowany i utrzymany, a ja go często używam! RestKit obejmuje wiele typowych przypadków, ale czuję się, jakbym walczył, gdy go używam, a większość źródeł danych, z którymi się spotykam, jest pełna dziwactw i problemów, które najlepiej radzić sobie z niestandardowym kodem. W moich ostatnich kilku aplikacjach po prostu używam wbudowanych konwerterów JSON i piszę kilka metod użytkowych.

Jeden wzór, którego zawsze używam, to uzyskanie sieć odwołuje główny wątek. Ostatnie aplikacje 4-5, które zrobiłem, skonfigurowały zadanie timera w tle za pomocą dispatch_source_create, które budzi się co jakiś czas i wykonuje zadania sieciowe w razie potrzeby. Musisz wykonać pewne czynności związane z bezpieczeństwem wątku i upewnić się, że kod modyfikujący interfejs użytkownika zostanie wysłany do głównego wątku. Pomaga to również w przeprowadzaniu/inicjalizacji w taki sposób, aby użytkownik nie czuł się obciążony lub opóźniony. Do tej pory działa to dość dobrze. Proponuję przyjrzeć się tym rzeczy.

Na koniec myślę, że w miarę jak pracujemy więcej i ewoluujemy SYSTEM OPERACYJNY, mamy tendencję do opracowywania lepszych rozwiązań. Zajęło mi lata, aby przezwyciężyć moje przekonanie, że muszę podążać za wzorcami i projektami, które inni ludzie twierdzą, że są obowiązkowe. Jeśli pracuję w kontekście, w którym jest to częścią lokalnej religii, mam na myśli najlepsze praktyki inżynierskie, to podążam za zwyczajami do listu, za to mi płacą. Ale rzadko stwierdzam, że po starszych projektach a wzorce to optymalne rozwiązanie. Zawsze staram się patrzeć na rozwiązanie przez pryzmat potrzeb biznesowych i budować architekturę dopasowaną do niego i utrzymywać rzeczy tak proste, jak to tylko możliwe. Kiedy czuję, że nie ma wystarczająco dużo, ale wszystko działa poprawnie, to jestem na dobrej drodze.

 6
Author: Fran K.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-20 09:33:19

Używam podejścia, które dostałem stąd: https://github.com/Constantine-Fry/Foursquare-API-v2 . przepisałem tę bibliotekę w języku Swift i widać podejście architektoniczne z tych części kodu:

typealias OpertaionCallback = (success: Bool, result: AnyObject?) -> ()

class Foursquare{
    var authorizationCallback: OperationCallback?
    var operationQueue: NSOperationQueue
    var callbackQueue: dispatch_queue_t?

    init(){
        operationQueue = NSOperationQueue()
        operationQueue.maxConcurrentOperationCount = 7;
        callbackQueue = dispatch_get_main_queue();
    }

    func checkIn(venueID: String, shout: String, callback: OperationCallback) -> NSOperation {
        let parameters: Dictionary <String, String> = [
            "venueId":venueID,
            "shout":shout,
            "broadcast":"public"]
        return self.sendRequest("checkins/add", parameters: parameters, httpMethod: "POST", callback: callback)
    }

    func sendRequest(path: String, parameters: Dictionary <String, String>, httpMethod: String, callback:OperationCallback) -> NSOperation{
        let url = self.constructURL(path, parameters: parameters)
        var request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = httpMethod
        let operation = Operation(request: request, callbackBlock: callback, callbackQueue: self.callbackQueue!)
        self.operationQueue.addOperation(operation)
        return operation
    }

    func constructURL(path: String, parameters: Dictionary <String, String>) -> NSURL {
        var parametersString = kFSBaseURL+path
        var firstItem = true
        for key in parameters.keys {
            let string = parameters[key]
            let mark = (firstItem ? "?" : "&")
            parametersString += "\(mark)\(key)=\(string)"
            firstItem = false
        }
    return NSURL(string: parametersString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding))
    }
}

class Operation: NSOperation {
    var callbackBlock: OpertaionCallback
    var request: NSURLRequest
    var callbackQueue: dispatch_queue_t

    init(request: NSURLRequest, callbackBlock: OpertaionCallback, callbackQueue: dispatch_queue_t) {
        self.request = request
        self.callbackBlock = callbackBlock
        self.callbackQueue = callbackQueue
    }

    override func main() {
        var error: NSError?
        var result: AnyObject?
        var response: NSURLResponse?

        var recievedData: NSData? = NSURLConnection.sendSynchronousRequest(self.request, returningResponse: &response, error: &error)

        if self.cancelled {return}

        if recievedData{
            result = NSJSONSerialization.JSONObjectWithData(recievedData, options: nil, error: &error)
            if result != nil {
                if result!.isKindOfClass(NSClassFromString("NSError")){
                    error = result as? NSError
            }
        }

        if self.cancelled {return}

        dispatch_async(self.callbackQueue, {
            if (error) {
                self.callbackBlock(success: false, result: error!);
            } else {
                self.callbackBlock(success: true, result: result!);
            }
            })
    }

    override var concurrent:Bool {get {return true}}
}

Zasadniczo istnieje podklasa NSOperation, która tworzy zapytanie NSURLRequest, parsuje odpowiedź JSON i dodaje blok zwrotny z wynikiem do kolejki. Główna klasa API konstruuje NSURLRequest, inicjuje tę podklasę NSOperation i dodaje ją do Kolejka

 4
Author: bzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-19 18:20:31

Stosujemy kilka podejść w zależności od sytuacji. Dla większości rzeczy AFNetworking jest najprostszym i najbardziej solidnym podejściem, ponieważ można ustawić nagłówki, przesłać dane wieloczęściowe, użyć GET, POST, PUT & DELETE i istnieje kilka dodatkowych kategorii Dla UIKit, które pozwalają na przykład ustawić obraz z adresu url. W złożonej aplikacji z wieloma wywołaniami czasami abstrakujemy to do wygodnej metody naszej własnej, która byłaby czymś w rodzaju:

-(void)makeRequestToUrl:(NSURL *)url withParameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;

Jest kilka sytuacje, w których AFNetworking nie jest jednak odpowiedni, takie jak tworzenie frameworka lub innego komponentu bibliotecznego, ponieważ AFNetworking może już znajdować się w innej bazie kodu. W tej sytuacji można użyć nsmutableurlrequest albo inline, jeśli wykonujesz pojedyncze wywołanie lub abstrakcyjne do klasy request / response.

 3
Author: Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-17 09:43:11

Przy projektowaniu aplikacji unikam singletonów. Są one typowe dla wielu ludzi, ale myślę, że można znaleźć bardziej eleganckie rozwiązania gdzie indziej. Zazwyczaj to, co robię, to buduję moje podmioty w CoreData, a następnie umieszczam mój kod REST w kategorii NSManagedObject. Jeśli na przykład chciałbym utworzyć i opublikować nowego Użytkownika, zrobiłbym to:

User* newUser = [User createInManagedObjectContext:managedObjectContext];
[newUser postOnSuccess:^(...) { ... } onFailure:^(...) { ... }];

Używam RESTKit do mapowania obiektów i inicjalizuję go przy starcie. Uważam, że przekierowanie wszystkich połączeń przez singleton jest strata czasu i dodaje dużo kotła, który nie jest potrzebny.

W Nsmanagedobject + Extensions.m:

+ (instancetype)createInContext:(NSManagedObjectContext*)context
{
    NSAssert(context.persistentStoreCoordinator.managedObjectModel.entitiesByName[[self entityName]] != nil, @"Entity with name %@ not found in model. Is your class name the same as your entity name?", [self entityName]);
    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:context];
}

W NSManagedObject+Networking.m:

- (void)getOnSuccess:(RESTSuccess)onSuccess onFailure:(RESTFailure)onFailure blockInput:(BOOL)blockInput
{
    [[RKObjectManager sharedManager] getObject:self path:nil parameters:nil success:onSuccess failure:onFailure];
    [self handleInputBlocking:blockInput];
}

Po co dodawać dodatkowe klasy pomocnicze, skoro można rozszerzyć funkcjonalność wspólnej klasy bazowej poprzez kategorie?

Jeśli jesteś zainteresowany bardziej szczegółowymi informacjami na temat mojego rozwiązania daj mi znać. Chętnie się podzielę.

 1
Author: Sandy Chapman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-19 16:29:08

Try https://github.com/kevin0571/STNetTaskQueue

Tworzenie żądań API w rozdzielonych klasach.

STNetTaskQueue zajmie się threadingiem i delegowaniem / oddzwanianiem.

Możliwość rozszerzenia dla różnych protokołów.

 0
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-05 03:08:36

Z czysto klasowego punktu widzenia, zazwyczaj masz coś takiego:

  • Twój kontroler widoku kontrolujący jeden lub więcej widoków
  • Klasa modelu danych - to naprawdę zależy od tego, z jaką liczbą rzeczywistych odrębnych podmiotów masz do czynienia i jak są ze sobą powiązane.

    Na przykład, jeśli masz tablicę elementów, które mają być wyświetlane w czterech różnych reprezentacjach( list, chart, graph itp.), będziesz miał jedną klasę modelu danych dla list of przedmioty, jeszcze jeden za przedmiot. Lista klasy pozycji będzie współdzielona przez cztery kontrolery widoków - wszystkie dzieci kontrolera paska kart lub kontrolera nav.

    Klasy modeli danych przydadzą się nie tylko w wyświetlaniu danych, ale także serializowaniu ich, w którym każda z nich może ujawnić swój własny format serializacji za pomocą metod eksportu JSON / XML / CSV (lub czegokolwiek innego).

  • Ważne jest, aby zrozumieć, że potrzebujesz również klasy API request builder mapowanie bezpośrednio z punktami końcowymi REST API. Załóżmy, że masz API, które loguje użytkownika - więc twoja klasa Login API builder utworzy POST JSON payload dla login api. W innym przykładzie, Klasa API request builder dla list of catalog items API utworzy ciąg zapytania GET dla odpowiedniego api i odpali zapytanie Rest GET.

    Te klasy API request builder zazwyczaj otrzymują dane z kontrolerów widoku, a także przekazują te same dane z powrotem do kontrolerów widoku w celu aktualizacji interfejsu użytkownika / inne operacje. Kontrolery widoku będą wtedy decydować, jak aktualizować obiekty modelu danych o te dane.

  • Wreszcie, serce reszty KLIENTA - klasa API data fetcher który jest nieświadomy wszelkiego rodzaju żądań API, które sprawia, że aplikacja. Ta klasa najprawdopodobniej będzie singletonem, ale jak zauważyli inni, nie musi to być singleton.

    Zauważ, że link jest tylko typową implementacją i nie bierze pod uwagę scenariusze, takie jak sesja, pliki cookie itp., ale wystarczy, aby przejść bez korzystania z frameworków innych firm.

 0
Author: Nirav Bhatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-18 20:06:06

To pytanie ma już wiele doskonałych i obszernych odpowiedzi, ale czuję, że muszę o tym wspomnieć, ponieważ nikt inny nie ma.

Alamofire dla Swift. https://github.com/Alamofire/Alamofire

Jest tworzony przez tych samych ludzi co AFNetworking, ale jest bardziej bezpośrednio zaprojektowany z myślą o Swift.

 0
Author: matt.writes.code,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-06 17:01:10