Jestem programistą iOS z pewnym doświadczeniem i to pytanie jest dla mnie bardzo interesujące. Widziałem wiele różnych zasobów i materiałów na ten temat, ale nadal jestem zdezorientowany. Jaka jest najlepsza architektura dla aplikacji sieciowej na iOS? Mam na myśli podstawowe abstrakcyjne ramy, wzorce, które pasują do każdej aplikacji sieciowej, niezależnie od tego, czy jest to mała aplikacja, która ma tylko kilka żądań serwera, czy złożony klient REST. Apple zaleca stosowanie MVC
jako podstawowego podejścia architektonicznego do wszystkich aplikacji na iOS, ale MVC
ani bardziej nowoczesnegoMVVM
wzorce nie wyjaśniają, gdzie umieścić kod logiki sieci i jak go ogólnie zorganizować.
Czy muszę opracować coś takiego MVCS
( S
dla Service
) i na tej Service
warstwie umieścić wszystkie API
żądania i inną logikę sieci, która z perspektywy może być naprawdę złożona? Po przeprowadzeniu badań znalazłem dwa podstawowe podejścia do tego. W tym przypadku zalecono utworzenie osobnej klasy dla każdego żądania sieciowego do usługi sieci web API
(takiej jak LoginRequest
klasa lub PostCommentRequest
klasa itd.), Która wszystkie dziedziczy po abstrakcyjnych klas żądań podstawowychAbstractBaseRequest
a także utworzenie globalnego menedżera sieci, który zawiera wspólny kod sieciowy i inne preferencje (może to być AFNetworking
personalizacja lubRestKit
strojenie, jeśli mamy złożone odwzorowania obiektów i trwałość, a nawet własną implementację komunikacji sieciowej ze standardowym API). Ale takie podejście wydaje mi się narzutem. Innym rozwiązaniem jest mieć jakiś singleton API
dyspozytora lub klasy menedżera jak w pierwszym podejściu, ale nie do tworzenia klas dla każdego żądania i zamiast do hermetyzacji każde żądanie jako metody instancji publicznej tej klasy menedżera, takich jak: fetchContacts
, loginUser
metod, itd. Tak, to jakie jest najlepszy i poprawny sposób? Czy są jeszcze inne ciekawe podejścia, których jeszcze nie znam?
I czy powinienem utworzyć kolejną warstwę dla wszystkich takich rzeczy sieciowych Service
, lub NetworkProvider
warstwy lub czegokolwiek na mojej MVC
architekturze, czy też ta warstwa powinna zostać zintegrowana (wstrzyknięta) z istniejącymi MVC
warstwami np. Model
?
Wiem, że istnieją piękne podejścia lub jak takie mobilne potwory, jak klient Facebooka lub klient LinkedIn, radzą sobie z wykładniczo rosnącą złożonością logiki sieci?
Wiem, że nie ma dokładnej i formalnej odpowiedzi na problem. Celem tego pytania jest zebranie najciekawszych podejść od doświadczonych programistów iOS . Najlepsze sugerowane podejście zostanie oznaczone jako zaakceptowane i nagrodzone nagrodą za reputację, inne zostaną pozytywnie ocenione. 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.
źródło
Odpowiedzi:
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. To bardzo kreatywna praca. Zawsze powinieneś wybierać najbardziej prostą i rozszerzalną architekturę, która będzie zrozumiała dla każdego programisty, który zacznie pracować nad twoim projektem lub dla innych programistów w 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 interesujące lub poprawne, ale wykorzystałem je w kilku projektach i jestem z niego zadowolony. Jest to hybrydowe podejście, o którym wspomniałeś powyżej, a także ulepszenia z moich własnych badań. Interesują mnie problemy budowania podejść, które łączą kilka dobrze znanych wzorców i idiomów. Myślę, że wiele wzorców korporacyjnych Fowlera można z powodzeniem zastosować w aplikacjach mobilnych. Oto lista najciekawszych, które możemy złożyć wniosku o utworzenie architektury aplikacji iOS ( moim zdaniem ): Warstwa usługi , Jednostka pracy , Zdalna fasada , obiektu przesyłania danych ,Brama , typ warstwy , przypadek specjalny , model domeny . Zawsze należy poprawnie zaprojektować warstwę modelu i zawsze nie zapominać o trwałości (może to znacznie zwiększyć wydajność aplikacji). MożeszCore Data
do tego użyć . Nie należy jednak zapominać, żeCore Data
nie jest to ORM ani baza danych, ale menedżer grafów obiektowych z trwałością jako dobrą opcją. Tak więc bardzo częstoCore Data
może być zbyt ciężki dla twoich potrzeb i możesz spojrzeć na nowe rozwiązania, takie jak Realm i Couchbase Lite , lub zbudować własną lekką warstwę mapowania / trwałości obiektów, opartą na surowym SQLite lub LevelDB. Radzę również zapoznać się z projektami opartymi na domenach i CQRS .Na początku myślę, że powinniśmy stworzyć kolejną warstwę dla sieci, ponieważ nie chcemy kontrolerów tłuszczu ani ciężkich, przytłoczonych modeli. Nie wierzę w te
fat model, skinny controller
rzeczy. Ale wierzę wskinny everything
podejście, ponieważ żadna klasa nie powinna być gruba, nigdy. Wszystkie sieci można generalnie wyodrębnić jako logikę biznesową, w związku z czym powinniśmy mieć kolejną warstwę, w której możemy to umieścić. Warstwa serwisowa jest tym, czego potrzebujemy:W naszym
MVC
królestwieService Layer
jest coś w rodzaju pośrednika między modelem domeny a kontrolerami. Istnieje dość podobna odmiana tego podejścia zwana MVCS, gdzie aStore
jest w rzeczywistości nasząService
warstwą.Store
vending instancje modelu i obsługuje tworzenie sieci, buforowanie itp. Chcę wspomnieć, że nie powinieneś pisać całej sieci i logiki biznesowej w warstwie usług. Można to również uznać za zły projekt. Aby uzyskać więcej informacji, zobacz modele domen Anemic i Rich . Niektóre metody serwisowe 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 niezbędne dla każdej nowoczesnej aplikacji, która współdziała z siecią i usługami internetowymi lub zawiera złożoną logikę interfejsu użytkownika.
ARCHITEKTURA
Najpierw tworzę
APIClient
klasę ogólną , która jest podklasą AFHTTPSessionManager . Jest to koń roboczy całej sieci w aplikacji: wszystkie klasy usług delegują do niego rzeczywiste żądania REST. Zawiera wszystkie dostosowania klienta HTTP, których potrzebuję w konkretnej aplikacji: przypinanie SSL, przetwarzanie błędów i tworzenie prostychNSError
obiektów ze szczegółowymi przyczynami niepowodzenia i opisami wszystkichAPI
błędów oraz błędów połączenia (w takim przypadku kontroler będzie mógł wyświetlać poprawne komunikaty dla użytkownika), ustawianie serializatorów żądań i odpowiedzi, nagłówków http i innych rzeczy związanych z siecią. Potem logicznie podzielić wszystkie wnioski API język subservices lub, bardziej poprawnie, microservices :UserSerivces
,CommonServices
,SecurityServices
,FriendsServices
i tak dalej, zgodnie z logiką biznesową, którą wdrażają. Każda z tych mikrousług jest odrębną klasą. Razem tworząService Layer
. Klasy te zawierają metody dla każdego żądania API, przetwarzają modele domen i zawsze zwracają aRACSignal
z analizowanym modelem odpowiedzi lubNSError
wywołującym.Chcę wspomnieć, że jeśli masz złożoną logikę serializacji modelu - utwórz dla niej kolejną warstwę: coś takiego jak Data Mapper, ale bardziej ogólnie, np. JSON / XML -> Model mapper. Jeśli masz pamięć podręczną: utwórz ją również jako osobną warstwę / usługę (nie należy mieszać logiki biznesowej z buforowaniem). Dlaczego? Ponieważ poprawna warstwa buforująca może być dość złożona z własnymi problemami. Ludzie stosują złożoną logikę, aby uzyskać prawidłowe, przewidywalne buforowanie, takie jak np. Buforowanie monoidalne z projekcjami opartymi na profpraktorach. Możesz przeczytać o tej pięknej bibliotece o nazwie Carlos, aby dowiedzieć się więcej. I nie zapominaj, że Core Data może naprawdę pomóc ci we wszystkich problemach z buforowaniem i pozwoli ci napisać mniej logiki. Ponadto, jeśli masz trochę logiki między repozytorium
NSManagedObjectContext
modelami żądań serwera a serwerami, możesz użyćWzorzec , który oddziela logikę, która pobiera dane i mapuje je do modelu encji od logiki biznesowej, która działa na model. Dlatego radzę używać wzorca repozytorium, nawet jeśli masz architekturę opartą na podstawowych danych. Repozytorium może abstrakcyjne rzeczy, jakNSFetchRequest
,NSEntityDescription
,NSPredicate
i tak dalej do prostych metod, takich jakget
czyput
.Po wszystkich tych czynnościach w warstwie usług osoba wywołująca (kontroler widoku) może wykonać złożone złożone operacje asynchroniczne z odpowiedzią: manipulowanie sygnałem, tworzenie łańcuchów, mapowanie itp. Za pomocą operacji
ReactiveCocoa
podstawowych lub po prostu subskrybować i wyświetlać wyniki w widoku . I wstrzyknąć z wstrzykiwania zależności We wszystkich tych klas usług moiAPIClient
, co przełoży konkretnego zgłoszenia serwisowego w odpowiednieGET
,POST
,PUT
,DELETE
, itd żądania do punktu końcowego REST. W takim przypadkuAPIClient
jest niejawnie przekazywany do wszystkich kontrolerów, można to jednoznacznie sparametryzować poprzezAPIClient
klasy usług. Może to mieć sens, jeśli chcesz użyć różnych dostosowańAPIClient
dla określonych klas usług, ale jeśli z jakichś powodów nie chcesz dodatkowych kopii lub masz pewność, że zawsze użyjesz jednej konkretnej instancji (bez dostosowań)APIClient
- zrób to singleton, ale NIE, proszę NIE „Czyń klasy usług jako singletony.Następnie każdy kontroler widoku ponownie z DI wstrzykuje potrzebną klasę usługi, wywołuje odpowiednie metody usługi i zestawia ich wyniki z logiką interfejsu użytkownika. Do wstrzykiwania zależności lubię używać BloodMagic lub bardziej zaawansowanego frameworka Typhoon . Nigdy nie używam singletonów,
APIManagerWhatever
klasy Bożej ani innych niewłaściwych rzeczy. Ponieważ jeśli zadzwonisz do swojej klasyWhateverManager
, oznacza to, że nie znasz jej celu i jest to zły wybór projektu . Singletony to również anty-wzór, aw większości przypadków (z wyjątkiem rzadkich) jest złym rozwiązaniem. Singleton należy rozważyć tylko wtedy, gdy wszystkie trzy z następujących kryteriów są spełnione:W naszym przypadku własność pojedynczej instancji nie stanowi problemu, a także nie potrzebujemy globalnego dostępu po podzieleniu naszego boga managera na usługi, ponieważ teraz tylko jeden lub kilka dedykowanych kontrolerów potrzebuje konkretnej usługi (np.
UserProfile
Potrzebuje kontroleraUserServices
itd.) .Zawsze powinniśmy szanować
S
zasadę w SOLID i stosować separację problemów , więc nie umieszczaj wszystkich metod obsługi i połączeń sieciowych w jednej klasie, ponieważ to szalone, szczególnie jeśli tworzysz dużą aplikację dla przedsiębiorstw. Dlatego powinniśmy rozważyć zastrzyk zależności 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 (sterowniki i zdarzenia) oraz parametry.Oto ogólny przepływ pracy mojej architektury według przykładów. Załóżmy, że mamy
FriendsViewController
, która wyświetla listę znajomych użytkownika i mamy opcję usunięcia z listy znajomych. W mojejFriendsServices
klasie tworzę metodę o nazwie:gdzie
Friend
jest obiekt modelu / domeny (lub może to być tylkoUser
obiekt, jeśli mają podobne atrybuty). Underhood tej metody parsowańFriend
doNSDictionary
parametrów JSONfriend_id
,name
,surname
,friend_request_id
i tak dalej. Zawsze używam biblioteki Mantle dla tego rodzaju szablonów i dla mojej warstwy modelu (parsowanie w przód iw tył, zarządzanie hierarchiami obiektów zagnieżdżonych w JSON i tak dalej). Po parsowania wywołujeAPIClient
DELETE
metodę dokonania faktycznej żądania REST i powracaResponse
wRACSignal
osobie dzwoniącej (FriendsViewController
w naszym przypadku), aby wyświetlić odpowiedni komunikat dla użytkownika lub cokolwiek.Jeśli nasza aplikacja jest bardzo duża, musimy rozdzielić naszą logikę jeszcze wyraźniej. Np. Nie zawsze dobrze jest łączyć
Repository
lub modelować logikę zService
jednym. Kiedy opisałem swoje podejście, powiedziałem, żeremoveFriend
metoda powinna znajdować się wService
warstwie, ale jeśli będziemy bardziej pedantyczni, zauważymy, że lepiej do niej należyRepository
. Pamiętajmy, czym jest repozytorium. Eric Evans podał dokładny opis w swojej książce [DDD]:A zatem
Repository
jest zasadniczo fasadą, która wykorzystuje semantykę stylu Kolekcji (Dodaj, Aktualizuj, Usuń) w celu zapewnienia dostępu do danych / obiektów. Dlatego, gdy masz coś takiego:getFriendsList
,getUserGroups
,removeFriend
można umieścić go wRepository
, ponieważ kolekcja podobny semantyki jest całkiem jasne tutaj. I kod jak:jest zdecydowanie logiką biznesową, ponieważ wykracza poza podstawowe
CRUD
operacje i łączy dwa obiekty domeny (Friend
iRequest
), dlatego należy ją umieścić wService
warstwie. Chcę też zauważyć: nie twórz niepotrzebnych abstrakcji . Mądrze stosuj wszystkie te podejścia. Ponieważ jeśli przytłoczysz swoją aplikację abstrakcjami, zwiększy to jej przypadkową złożoność, a złożoność powoduje więcej problemów w systemach oprogramowania niż cokolwiek innegoOpisuję wam „stary” przykład Celu C, ale to podejście można bardzo łatwo dostosować do języka Swift z dużo większą liczbą ulepszeń, ponieważ ma ono więcej przydatnych funkcji i funkcjonalny cukier. Bardzo polecam skorzystać z tej biblioteki: Moya . Pozwala stworzyć bardziej elegancką
APIClient
warstwę (nasz koń roboczy, jak pamiętasz). Teraz naszAPIClient
dostawca będzie typem wartości (wyliczeniem) z rozszerzeniami zgodnymi z protokołami i wykorzystującymi dopasowanie wzorca destrukcji. Szybkie wyliczanie + dopasowanie wzorców pozwala nam tworzyć algebraiczne typy danych, jak w klasycznym programowaniu funkcjonalnym. Nasze mikrousługi wykorzystają tego ulepszonegoAPIClient
dostawcę, jak w zwykłym podejściu Celu C. Do warstwy modelu zamiastMantle
możesz użyć biblioteki ObjectMapperlub lubię używać bardziej eleganckiej i funkcjonalnej biblioteki Argo .Opisałem więc moje ogólne podejście architektoniczne, które, jak sądzę, można dostosować do każdego zastosowania. Oczywiście można wprowadzić znacznie więcej ulepszeń. Radzę nauczyć się programowania funkcjonalnego, ponieważ możesz z niego wiele skorzystać, ale nie posuwaj się za daleko. Wyeliminowanie nadmiernego, wspólnego, globalnego stanu mutable, stworzenie niezmiennego modelu domeny lub stworzenie czystych funkcji bez zewnętrznych skutków ubocznych jest ogólnie dobrą praktyką, a nowy
Swift
język zachęca do tego. Ale zawsze pamiętaj, że przeciążanie kodu ciężkimi czystymi wzorcami funkcjonalnymi, podejście teoretyczne do kategorii jest złym pomysłem, ponieważ inne programiści będą czytać i wspierać Twój kod, i mogą być sfrustrowani lub przerażeniprismatic profunctors
i tego rodzaju rzeczy w niezmiennym modelu. To samo dotyczyReactiveCocoa
: nieRACify
kod za bardzo , ponieważ może stać się bardzo nieczytelny, szczególnie dla początkujących. Użyj go, gdy naprawdę może 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ć.źródło
". I don't like singletons. I have an opinion that if you decided to use singletons in your project you should have at least three criteria why you do this (I edited my answer). So I inject them (lazy of course and not each time, but
raz `) w każdym kontrolerzeZgodnie z celem tego pytania chciałbym opisać nasze podejście do architektury.
Podejście architektury
Nasza ogólna architektura aplikacji na iOS opiera się na następujących wzorcach: warstwy usług , MVVM , wiązanie danych interfejsu użytkownika , wstrzykiwanie zależności ; oraz paradygmat programowania funkcjonalnego reaktywnego .
Możemy podzielić typową aplikację skierowaną do konsumentów na następujące logiczne warstwy:
Warstwa montażowa jest punktem początkowym naszej aplikacji. Zawiera kontener wstrzykiwania 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 stron trzecich i tak dalej). W tym celu korzystamy z biblioteki Typhoon .
Warstwa modelu zawiera klasy modeli domen, walidacje, mapowania. Używamy biblioteki Mantle do mapowania naszych modeli: obsługuje serializację / deserializację do
JSON
formatu iNSManagedObject
modeli. Do sprawdzania poprawności i reprezentacji formularzy naszych modeli używamy bibliotek FXForm i FXModelValidation .Warstwa usług deklaruje usługi, których używamy do interakcji z systemami zewnętrznymi w celu wysyłania lub odbierania danych reprezentowanych w naszym modelu domeny. Zwykle mamy więc usługi komunikacji z interfejsami API serwera (na jednostkę), usługi przesyłania wiadomości (takie jak PubNub ), usługi pamięci masowej (jak Amazon S3) itp. Zasadniczo usługi obejmują obiekty dostarczane przez SDK (na przykład SDK PubNub) lub implementują własną komunikację logika. Do ogólnych sieci korzystamy z biblioteki AFNetworking .
Celem warstwy pamięci jest zorganizowanie lokalnego przechowywania danych na urządzeniu. Używamy do tego Core Data lub Realm (oba mają zalety i wady, decyzja o tym, czego użyć, zależy od konkretnych specyfikacji). Do konfiguracji danych podstawowych używamy biblioteki MDMCoreData i szeregu klas - magazynów - (podobnych do usług), które zapewniają dostęp do lokalnej pamięci dla każdej jednostki. W Realm używamy podobnych magazynów, aby mieć dostęp do lokalnego magazynu.
Warstwa menedżerów to miejsce, w którym żyją nasze abstrakcje / opakowania.
W roli menedżera może być:
Tak więc w roli menedżera może znajdować się 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.
Warstwa koordynatorów zapewnia obiekty zależne od obiektów z innych warstw (usługa, pamięć, model) w celu połączenia ich logiki w jedną sekwencję pracy wymaganą dla określonego modułu (funkcja, ekran, historia użytkownika lub doświadczenie użytkownika). Zwykle łączy operacje asynchroniczne i wie, jak reagować na ich przypadki powodzenia i niepowodzenia. Jako przykład możesz wyobrazić sobie funkcję przesyłania wiadomości i odpowiedni
MessagingCoordinator
obiekt. Obsługa operacji wysyłania wiadomości może wyglądać następująco:Na każdym z powyższych kroków odpowiednio obsługiwany jest błąd.
Warstwa interfejsu użytkownika składa się z następujących podwarstw:
Aby uniknąć kontrolerów Massive View używamy wzorca MVVM i implementujemy logikę potrzebną do prezentacji interfejsu użytkownika w ViewModels. ViewModel zwykle ma koordynatorów i menedżerów jako zależności. ViewModels używane przez ViewControllers i niektóre rodzaje widoków (np. Komórki widoku tabeli). Klej pomiędzy ViewControllers i ViewModels to Powiązanie danych i Wzorzec poleceń. Aby mieć ten klej, używamy biblioteki ReactiveCocoa .
Używamy również ReactiveCocoa i jego
RACSignal
koncepcji jako interfejsu i typu wartości zwracanej przez wszystkich koordynatorów, usługi, metody przechowywania. To pozwala nam łączyć operacje, uruchamiać je równolegle lub szeregowo oraz wiele innych przydatnych rzeczy dostarczanych przez ReactiveCocoa.Staramy się implementować nasze zachowanie interfejsu użytkownika w sposób deklaratywny. Wiązanie danych i automatyczny układ bardzo pomagają w osiągnięciu tego celu.
Warstwa infrastruktury zawiera wszystkie pomocniki, rozszerzenia, narzędzia potrzebne do pracy aplikacji.
To podejście działa dobrze dla nas i dla tych typów aplikacji, które zwykle tworzymy. Ale powinieneś zrozumieć, że jest to tylko subiektywne podejście, które należy dostosować / zmienić dla konkretnych celów zespołu.
Mam nadzieję, że to ci pomoże!
Więcej informacji na temat procesu tworzenia systemu iOS można znaleźć w tym poście na blogu Rozwój systemu iOS jako usługa
źródło
Ponieważ wszystkie aplikacje na iOS są różne, myślę, że należy tutaj rozważyć różne podejścia, ale zwykle idę w ten sposób:
Utwórz klasę centralnego menedżera (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:
Dla przypomnienia używam 2 głównych bibliotek / frameworków, ReactiveCocoa i AFNetworking. ReactiveCocoa doskonale obsługuje asynchroniczne odpowiedzi sieciowe, możesz to zrobić (sendNext :, sendError :, itd.).
Ta metoda wywołuje interfejs API, pobiera wyniki i wysyła je przez RAC w formacie „surowym” (np. NSArray, co zwraca sieć AF).
Następnie metoda podobna do tej,
getStuffList:
która wywołała powyższą metodę, subskrybuje jego sygnał, analizuje surowe dane w obiekty (za pomocą czegoś takiego jak Motis) i wysyła obiekty jeden po drugim do osoby wywołującej (getStuffList:
i podobne metody również zwracają sygnał, który kontroler może subskrybować) ).Subskrybowany kontroler odbiera obiekty przez
subscribeNext:
blok i obsługuje je.Próbowałem wielu sposobów w różnych aplikacjach, ale ten działał najlepiej ze wszystkich, więc ostatnio używałem go w kilku aplikacjach, 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 pomaga, chciałbym usłyszeć opinie innych na temat mojego podejścia i być może, jak inni myślą, że można to poprawić.
źródło
+ (void)getAllUsersWithSuccess:(void(^)(NSArray*))success failure:(void(^)(NSError*))failure;
i- (void)postWithSuccess:(void(^)(instancetype))success failure:(void(^)(NSError*))failure;
które zrobić niezbędne przygotowania, a następnie zadzwonić do kierownika poprzez API.W mojej sytuacji zwykle używam biblioteki ResKit do konfigurowania warstwy sieciowej. Zapewnia łatwą w użyciu analizę. Zmniejsza to mój wysiłek związany z konfigurowaniem mapowania dla różnych odpowiedzi i innych rzeczy.
Dodaję tylko trochę kodu, aby automatycznie ustawić mapowanie. Definiuję klasę podstawową dla moich modeli (nie protokołu z powodu dużej ilości kodu, aby sprawdzić, czy jakaś metoda jest zaimplementowana, czy nie, i mniej kodu w samych modelach):
MappableEntry.h
MappableEntry.m
Relacje to obiekty, które w odpowiedzi reprezentują obiekty zagnieżdżone:
Związek Obiekt.h
Związek Obiekt.m
Następnie konfiguruję mapowanie RestKit w następujący sposób:
ObjectMappingInitializer.h
ObjectMappingInitializer.m
Przykład implementacji MappableEntry:
Użytkownik. H
Użytkownik.m
Teraz o pakowaniu żądań:
Mam plik nagłówkowy z definicją bloków, aby zmniejszyć długość linii we wszystkich klasach APIRequest:
APICallbacks.h
I przykład mojej klasy APIRequest, której używam:
LoginAPI.h
LoginAPI.m
A wszystko, co musisz zrobić w kodzie, wystarczy zainicjować obiekt API i wywołać go w dowolnym momencie:
SomeViewController.m
Mój kod nie jest idealny, ale łatwo go ustawić raz i używać w różnych projektach. Jeśli jest to interesujące dla kogoś, mógłbym poświęcić trochę czasu i stworzyć uniwersalne rozwiązanie gdzieś na GitHub i CocoaPods.
źródło
Moim zdaniem cała architektura oprogramowania jest napędzana potrzebą. Jeśli jest to do celów edukacyjnych lub osobistych, wybierz główny cel i poprowadź architekturę. Jeśli jest to praca najemna, potrzeba biznesu jest najważniejsza. Sztuką jest, aby nie pozwolić błyszczącym rzeczom odciągnąć cię od rzeczywistych potrzeb. Trudno mi to zrobić. W tym biznesie zawsze pojawiają się nowe błyszczące rzeczy i wiele z nich jest nieprzydatnych, ale nie zawsze można to powiedzieć z góry. Skoncentruj się na potrzebie i bądź gotów porzucić złe wybory, jeśli możesz.
Na przykład ostatnio zrobiłem szybki prototyp aplikacji do udostępniania zdjęć dla lokalnej firmy. Ponieważ potrzebą firmy było zrobienie czegoś szybkiego i brudnego, architektura zakończyła się kodem iOS, który wyświetlał kamerę, oraz kodem sieciowym dołączonym do przycisku wysyłania, który przesłał obraz do sklepu S3 i napisał w domenie SimpleDB. Kod był trywialny, a jego koszt minimalny, a klient ma skalowalną kolekcję zdjęć dostępną w Internecie za pomocą wywołań REST. Tania i głupia, aplikacja miała wiele wad i czasami blokowała interfejs użytkownika, ale zrobienie więcej dla prototypu byłoby marnotrawstwem i pozwala im wdrożyć do swoich pracowników i łatwo wygenerować tysiące zdjęć testowych bez wydajności lub skalowalności obawy. Nędzna architektura, ale idealnie pasująca do potrzeb i kosztująca.
Inny projekt obejmował wdrożenie lokalnej bezpiecznej bazy danych, która synchronizuje się z systemem firmy w tle, gdy sieć jest dostępna. Stworzyłem synchronizator w tle, który używał RestKit, ponieważ wydawało się, że ma wszystko, czego potrzebowałem. Ale musiałem napisać tyle niestandardowego kodu dla RestKit, aby poradzić sobie z idiosynkratycznym JSON, że mogłem to zrobić szybciej, pisząc własny JSON do transformacji CoreData. Jednak klient chciał wprowadzić tę aplikację do domu i czułem, że RestKit będzie podobny do frameworków, których używali na innych platformach. Czekam, czy to dobra decyzja.
Ponownie, moim problemem jest skupienie się na potrzebie i pozwolenie na określenie architektury. Staram się jak cholera, aby uniknąć korzystania z pakietów stron trzecich, ponieważ powodują one koszty, które pojawiają się dopiero po pewnym czasie działania aplikacji. Staram się unikać tworzenia hierarchii klas, ponieważ rzadko się to opłaca. 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 rzadko są to pakiety stron trzecich. Powiedziawszy to, uważam, że AF Networking jest zbyt przydatny, aby ignorować i dobrze zorganizowany, dobrze skomentowany i utrzymywany, i często go używam! RestKit obejmuje wiele typowych przypadków, ale czuję się, jakbym walczył, gdy go używam, a większość źródeł danych, które napotykam, jest pełna dziwactw i problemów, które najlepiej rozwiązać przy pomocy niestandardowego kodu. W moich ostatnich aplikacjach używam tylko wbudowanych konwerterów JSON i piszę kilka metod narzędziowych.
Jednym ze wzorów, których zawsze używam, jest usunięcie połączeń sieciowych z głównego wątku. Ostatnie 4-5 aplikacji, które wykonałem, skonfigurowałem zadanie timera w tle przy użyciu dispatch_source_create, który budzi się tak często i wykonuje zadania sieciowe w razie potrzeby. Musisz wykonać pewne prace związane z bezpieczeństwem wątków i upewnić się, że kod modyfikujący interfejs użytkownika zostanie wysłany do głównego wątku. Pomaga także w wykonywaniu procesu instalacji / inicjalizacji w taki sposób, aby użytkownik nie czuł się obciążony ani opóźniony. Do tej pory działało to dość dobrze. Proponuję zajrzeć do tych rzeczy.
Wreszcie myślę, że w miarę jak pracujemy coraz więcej i ewoluuje system operacyjny, mamy tendencję do opracowywania lepszych rozwiązań. Wiele lat zajęło mi przełamanie mojego przekonania, że muszę przestrzegać wzorów i wzorów, które inni ludzie uważają za obowiązkowe. Jeśli pracuję w kontekście, w którym jest to część lokalnej religii, mam na myśli najlepsze praktyki inżynieryjne w departamencie, to przestrzegam zwyczajów do listu, za to mi płacą. Ale rzadko stwierdzam, że podążanie za starszymi projektami i wzorami jest optymalnym rozwiązaniem. Zawsze staram się patrzeć na rozwiązanie przez pryzmat potrzeb biznesowych i budować architekturę dopasowaną do niego i utrzymywać rzeczy tak proste, jak tylko mogą być. Kiedy wydaje mi się, że jest za mało, ale wszystko działa poprawnie, to jestem na dobrej drodze.
źródło
Korzystam z podejścia, które dostałem stąd: https://github.com/Constantine-Fry/Foursquare-API-v2 . Ponownie napisałem tę bibliotekę w Swift i możesz zobaczyć podejście architektoniczne z tych części kodu:
Zasadniczo istnieje podklasa NSOperation, która tworzy NSURLRequest, analizuje odpowiedź JSON i dodaje blok zwrotny z wynikiem do kolejki. Główna klasa API konstruuje NSURLRequest, inicjuje podklasę NSOperation i dodaje ją do kolejki.
źródło
Stosujemy kilka podejść w zależności od sytuacji. W większości przypadków AFNetworking to najprostsze i najbardziej niezawodne podejście, w którym można ustawiać nagłówki, przesyłać dane wieloczęściowe, używać GET, POST, PUT & DELETE, a dla UIKit jest wiele dodatkowych kategorii, które pozwalają na przykład ustawić obraz z adres URL. W złożonej aplikacji z dużą liczbą połączeń czasami streszczamy to w oparciu o naszą własną wygodną metodę, która wyglądałaby mniej więcej tak:
Istnieje kilka sytuacji, w których AFNetworking nie jest odpowiedni, na przykład w przypadku tworzenia frameworka lub innego komponentu biblioteki, ponieważ AFNetworking może już znajdować się w innej bazie kodu. W tej sytuacji użyłbyś NSMutableURLRequest albo wbudowany, jeśli wykonujesz pojedyncze wywołanie, albo abstrakcji w klasę żądania / odpowiedzi.
źródło
Podczas projektowania aplikacji unikam singletonów. Są typowe dla wielu osób, ale myślę, że bardziej eleganckie rozwiązania można znaleźć gdzie indziej. Zazwyczaj to, co robię, to budowanie moich jednostek w CoreData, a następnie umieszczanie mojego kodu REST w kategorii NSManagedObject. Gdybym na przykład chciał utworzyć i wysłać nowego użytkownika POST, zrobiłbym to:
Używam RESTKit do mapowania obiektów i inicjalizuję go podczas uruchamiania. Przekierowanie wszystkich twoich połączeń przez singleton jest stratą czasu i dodaje dużo płyty kotłowej, która nie jest potrzebna.
W NSManagedObject + Extensions.m:
W NSManagedObject + Networking.m:
Po co dodawać dodatkowe klasy pomocnicze, kiedy można rozszerzyć funkcjonalność wspólnej klasy podstawowej o kategorie?
Jeśli jesteś zainteresowany bardziej szczegółowymi informacjami na temat mojego rozwiązania, daj mi znać. Z przyjemnością się dzielę.
źródło
Spróbuj https://github.com/kevin0571/STNetTaskQueue
Twórz żądania API w oddzielnych klasach.
STNetTaskQueue zajmie się wątkami i przekaże / oddzwoni.
Rozszerzalny dla różnych protokołów.
źródło
Z czysto klasowego punktu widzenia zazwyczaj będziesz mieć coś takiego:
Klasa modelu danych - To naprawdę zależy od tego, z iloma rzeczywistymi odrębnymi bytami masz do czynienia i jak są one powiązane.
Na przykład, jeśli masz tablicę elementów do wyświetlenia w czterech różnych reprezentacjach (lista, wykres, wykres itp.), Będziesz mieć jedną klasę modelu danych dla listy elementów, i jeszcze jedną dla pozycji. TheLista klasie pozycja zostanie podzielona przez cztery widok kontrolerów - wszystkich dzieci kontrolera bar tab lub kontrolerem nawigacyjnym.
Klasy modeli danych przydadzą się nie tylko do wyświetlania danych, ale także do szeregowania ich, przy czym każda z nich może ujawnić swój własny format serializacji za pomocą metod eksportu JSON / XML / CSV (lub cokolwiek innego).
Ważne jest, aby zrozumieć, że potrzebujesz także klas konstruktorów żądań API które są mapowane bezpośrednio na punkty końcowe interfejsu API REST. Załóżmy, że masz interfejs API, który loguje użytkownika - więc klasa konstruktora interfejsu API logowania utworzy ładunek POST JSON dla interfejsu API logowania. W innym przykładzie klasa narzędzia budującego żądania API dla listy elementów katalogu API utworzy ciąg zapytania GET dla odpowiedniego interfejsu API i uruchomi zapytanie REST GET.
Te klasy konstruktorów żądań API zwykle otrzymują dane od kontrolerów widoku, a także przekazują te same dane z powrotem do kontrolerów widoku w celu aktualizacji interfejsu użytkownika / innych operacji. Wyświetl kontrolery następnie zdecydują, jak zaktualizować obiekty modelu danych o te dane.
Wreszcie, serce klienta REST - klasa modułu pobierania danych API, która jest nieświadoma wszystkich żądań API twoich aplikacji. Ta klasa będzie prawdopodobnie singletonem, ale jak zauważyli inni, nie musi to być singleton.
Pamiętaj, że link jest tylko typową implementacją i nie bierze pod uwagę scenariuszy takich jak sesja, pliki cookie itp., Ale wystarcza, abyś mógł zacząć bez korzystania z zewnętrznych platform.
źródło
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 te same osoby co AFNetworking, ale jest zaprojektowany bardziej bezpośrednio z myślą o Swift.
źródło
Myślę, że na razie średni projekt korzysta z architektury MVVM, a duży projekt korzysta z architektury VIPER i stara się to osiągnąć
I Podejścia architektoniczne do budowania aplikacji sieciowych iOS (klienci REST)
Problem separacji dla czystego i czytelnego kodu pozwala uniknąć powielania:
inwersja zależności
Główny odpowiedzialny:
Znajdziesz tutaj architekturę GitHub MVVM z pozostałym API Swift Project
źródło
W inżynierii oprogramowania mobilnego najczęściej stosowane są wzorce Clean Architecture + MVVM i Redux.
Czysta architektura + MVVM składa się z 3 warstw: domeny, prezentacji, warstw danych. W przypadku gdy warstwa prezentacji i warstwa repozytoriów danych zależą od warstwy domeny:
Warstwa prezentacji składa się z ViewModels and Views (MVVM):
W tym artykule znajduje się bardziej szczegółowy opis Clean Architecture + MVVM https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
źródło