Найкращі архітектурні підходи для створення мережевих додатків iOS (клієнти REST)


323

Я розробник iOS, який має певний досвід, і це питання мені дуже цікаво. Я побачив багато різноманітних ресурсів та матеріалів на цю тему, але все-таки все переплутався. Яка найкраща архітектура для мережевого додатка iOS? Я маю на увазі основні абстрактні рамки, шаблони, які підійдуть для кожної мережевої програми, будь то невелика програма, у якої є лише кілька запитів сервера, або складний REST-клієнт. Apple рекомендує використовувати MVCяк базовий архітектурний підхід для всіх застосунків iOS, але ні MVCбільш сучасні MVVMзразки не пояснюють, куди слід поставити мережевий логічний код і як його організувати взагалі.

Чи потрібно мені розробити щось на кшталт MVCS( Sдля Service) і в цей Serviceшар покласти всі APIзапити та іншу логіку мереж, що в перспективі може бути справді складним? Провівши деякі дослідження, я знайшов для цього два основних підходи. Тут було рекомендовано створити окремий клас для кожного мережевого запиту до веб-сервісу API(наприклад, LoginRequestклас чи PostCommentRequestклас тощо), який успадковується від абстрактного класу базового запиту, AbstractBaseRequestа також створити менеджер глобальної мережі, який інкапсулює загальний мережевий код і інші налаштування (це може бути AFNetworkingналаштування абоRestKitналаштування, якщо у нас є складні об'єктні відображення та стійкість, або навіть власна мережева комунікаційна реалізація зі стандартним API). Але такий підхід для мене здається непосильним. Інший підхід полягає в якому - то одноплідних APIдиспетчері або клас менеджера , як і в першому підході, але не створювати класи для кожного запиту і замість того, щоб инкапсулировать кожен запит як метод екземпляр цього публічного менеджера класу , як: fetchContacts, loginUserметоди і т.д. Отже, що найкращий і правильний спосіб? Чи є інші цікаві підходи, яких я ще не знаю?

І чи слід створити ще один шар для всіх таких мережевих речей, як Service, наприклад , NetworkProviderшар чи будь-який інший поверх моєї MVCархітектури, або цей шар повинен бути інтегрований (введений) у існуючі MVCшари, наприклад Model?

Я знаю, що існують прекрасні підходи, або як тоді такі мобільні монстри, як клієнт Facebook або клієнт LinkedIn, справляються з експоненціально зростаючою складністю логіки мереж?

Я знаю, що немає точної та формальної відповіді на проблему. Мета цього питання - зібрати найцікавіші підходи від досвідчених розробників iOS . Найкращий запропонований підхід буде позначений як прийнятий та нагороджений репутацією, інші - за схвалення. Це здебільшого теоретичне та дослідницьке питання. Я хочу зрозуміти базовий, абстрактний і правильний архітектурний підхід для мережевих додатків в iOS. Сподіваюся на детальне пояснення від досвідчених розробників.


14
Це не питання "списку покупок"? У мене просто було запитання, яке було проголосовано в пеклі, і воно було закрите, оскільки було зазначено питання "що найкраще", що викликає занадто багато неконструктивних дискусій. Що робить цей список списку покупок гарним питанням, вартим грошей та грошей, а інші закриваються?
Елвін Томпсон

1
Зазвичай мережна логіка переходить у контролер, який змінює об'єкт моделі та повідомляє будь-якого делегата чи спостерігача.
чудовий

1
Дуже цікаві запитання та відповіді. Після 4 років кодування iOS та спроби знайти найкрасивіший спосіб додати мережевий шар у додаток. Який клас повинен нести відповідальність за керування мережевим запитом? Відповіді нижче справді доречні. Дякую
darksider

@JoeBlow це неправда. Індустрія мобільних додатків все ще дуже покладається на зв'язок сервер-клієнт.
сард

Відповіді:


327

I want to understand basic, abstract and correct architectural approach for networking applications in iOS: для побудови архітектури додатків немає "найкращого" чи "найправильнішого" підходу. Це дуже творча робота. Ви завжди повинні вибирати найпростішу та розширювану архітектуру, що буде зрозуміло будь-якому розробнику, який починає працювати над вашим проектом, або іншим розробникам у вашій команді, але я згоден, що тут може бути "хороший" і "поганий" "архітектура.

Ви сказали: collect the most interesting approaches from experienced iOS developersя не вважаю, що мій підхід є найцікавішим або правильним, але я його використав у кількох проектах і задоволений ним. Це гібридний підхід із тих, про кого ви згадали вище, а також із вдосконаленнями моїх власних досліджень. Мені цікаві проблеми побудови підходів, які поєднують кілька відомих закономірностей та ідіом. Я думаю, що багато моделей підприємств Фоулера можна успішно застосувати до мобільних додатків. Ось перелік найцікавіших з них, які ми можемо застосувати для створення архітектури додатків для iOS ( на мою думку ): Службовий шар , блок роботи , віддалений фасад , об’єкт передачі даних ,Шлюз , супертип типу шару , особливий кейс , модель домену . Ви завжди повинні правильно розробити шар моделі та не забувати про стійкість (це може значно підвищити ефективність вашої програми). Ви можете використовувати Core Dataдля цього. Але не слід забувати, що Core Dataце не ОРМ чи база даних, а керований графік об'єктів із наполегливістю як хороший варіант цього. Отже, дуже часто це Core Dataможе бути занадто важко для ваших потреб, і ви можете подивитися на нові рішення, такі як Realm і Couchbase Lite , або створити власний легкий об’єктний шар відображення / стійкість на основі сирого SQLite або LevelDB. Також я раджу ознайомитись із дизайном, керованим доменом та CQRS .

Спочатку, я думаю, ми повинні створити ще один шар для створення мереж, тому що ми не хочемо жирових контролерів або важких переповнених моделей. Я не вірю в ці fat model, skinny controllerречі. Але я вірю в skinny everythingпідхід, тому що жоден клас не повинен бути жирним ніколи. Всі мережі можна взагалі абстрагувати як бізнес-логіку, отже, у нас повинен бути ще один шар, де ми можемо це розмістити. Сервісний шар - це те, що нам потрібно:

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

У нашій MVCцарині Service Layerце щось на зразок посередника між доменною моделлю та контролерами. Існує досить схожа варіація цього підходу під назвою MVCS, де a Store- насправді наш Serviceшар. StoreПродає екземпляри моделей і обробляє мережу, кешування тощо. Хочу зазначити, що ви не повинні писати всю свою мережеву та ділову логіку на своєму сервісному рівні. Це також може розглядатися як погана конструкція. Для отримання додаткової інформації дивіться моделі Anemic and Rich домена. Деякі методи обслуговування та бізнес-логіку можна обробляти в моделі, тому це буде «багата» (з поведінкою) модель.

Я завжди широко використовую дві бібліотеки: AFNetworking 2.0 та ReactiveCocoa . Я думаю, що це необхідно для будь-якого сучасного додатку, який взаємодіє з мережею та веб-сервісами або містить складну логіку інтерфейсу.

АРХИТЕКТУРА

Спочатку я створюю загальний APIClientклас, який є підкласом AFHTTPSessionManager . Це робочий коник усіх мереж у додатку: усі класи сервісів делегують йому фактичні REST-запити. Він містить усі налаштування клієнта HTTP, які мені потрібні в конкретній програмі: фіксація SSL, обробка помилок та створення простих NSErrorоб'єктів з детальними причинами відмов та описом всіх APIта помилок підключення (у такому випадку контролер зможе показувати правильні повідомлення для користувача), налаштування серіалізаторів запитів та відповідей, заголовків http та інших матеріалів, пов’язаних із мережею. Тоді я логічно розділити всі запити API в подсервіси або, вірніше, microservices : UserSerivces, CommonServices, SecurityServices,FriendsServicesтощо, відповідно до бізнес-логіки, яку вони реалізують. Кожен з цих мікросервісів - окремий клас. Вони разом утворюють а Service Layer. Ці класи містять методи для кожного запиту API, моделей доменних процесів і завжди повертає a RACSignalз проаналізованою моделлю відповіді або NSErrorабоненту.

Хочу зазначити, що якщо у вас є складна логіка серіалізації моделі - тоді створіть для неї ще один шар: щось на зразок Data Mapper, але більш загальне, наприклад, JSON / XML -> Model mapper. Якщо у вас є кеш: тоді також створіть його як окремий шар / службу (не слід змішувати бізнес-логіку з кешуванням). Чому? Тому що правильний кеширующий шар може бути досить складним із власними дітками. Люди реалізують складну логіку, щоб отримати дійсне, передбачуване кешування, як-от, наприклад, моноїдне кешування з проекціями на основі профункціонерів. Ви можете прочитати про цю прекрасну бібліотеку під назвою Карлос, щоб зрозуміти більше. І не забувайте, що основні дані дійсно можуть допомогти вам у вирішенні всіх проблем кешування і дозволять писати менше логіки. Крім того, якщо у вас є певна логіка між NSManagedObjectContextмоделями та запитами сервера, ви можете використовуватиШаблон сховища , який відокремлює логіку, яка отримує дані, і відображає їх в модель сутності від бізнес-логіки, яка діє на модель. Отже, я раджу використовувати шаблон репозиторію, навіть якщо у вас є архітектура на основі основних даних. Repository тазів абстрактні речі, як NSFetchRequest, NSEntityDescription, NSPredicateі так далі для простих методів , таких як getабо put.

Після всіх цих дій на рівні Сервісу абонент (контролер перегляду) може виконати деякі складні асинхронні речі з відповіддю: маніпуляції сигналом, ланцюжок, відображення карти тощо за допомогою ReactiveCocoaпримітивів, або просто підписатися на нього та показати результати у поданні . Я впорснути з Injection Dependency у всіх цих службах класів моїх APIClient, яка переведе конкретний виклик служби у відповідному GET, POST, PUT, DELETEі т.д. запит на REST кінцевої точку. У цьому випадку APIClientпередається неявно всім контролерам, ви можете зробити це явним шляхом параметризованим на рівні APIClientкласів обслуговування. Це може мати сенс, якщо ви хочете використовувати різні налаштуванняAPIClientдля конкретних класів обслуговування, але якщо ви з якихось причин не хочете зайвих копій або ви впевнені, що завжди будете використовувати один конкретний екземпляр (без налаштувань) APIClient- зробіть це однократним, але НЕ, будь ласка, DON 'Зробіть класи обслуговування як одиночні.

Потім кожен контролер перегляду знову разом із DI вводить необхідний йому клас обслуговування, викликає відповідні методи обслуговування та складає їх результати з логікою інтерфейсу користувача. Для введення залежності я люблю використовувати BloodMagic або більш потужний фреймворк Тайфун . Я ніколи не використовую одиночних APIManagerWhateverкласів, класів Бога чи інших неправильних речей. Тому що якщо ви телефонуєте своєму класу WhateverManager, це означає, що ви не знаєте його призначення, і це поганий вибір дизайну . Синглтонтон також є антидіаграном, і в більшості випадків (крім рідкісних) є неправильним рішенням. Синглтон слід розглядати лише в тому випадку, якщо всі три наступні критерії задоволені:

  1. Право власності на єдиний екземпляр не може бути розумно присвоєний;
  2. Бажана лінива ініціалізація;
  3. Глобальний доступ не передбачений інакше.

У нашому випадку право власності на один примірник не є проблемою, а також нам не потрібен глобальний доступ після того, як ми розділили нашого менеджера богів на сервіси, тому що зараз лише один або кілька виділених контролерів потребують певної послуги (наприклад, UserProfileпотреби контролера UserServicesтощо) .

Ми завжди повинні дотримуватися Sпринципу в SOLID і використовувати розділення проблем , тому не ставте всі свої методи обслуговування та мережеві дзвінки в один клас, тому що це божевільно, особливо якщо ви розробляєте велику корпоративну програму. Ось чому ми повинні розглянути питання щодо введення залежності та надання послуг. Я вважаю такий підхід сучасним та пост-ОО . У цьому випадку ми розділили наш додаток на дві частини: логіка управління (контролери та події) та параметри.

Одним із типів параметрів були б звичайні параметри даних. Це те, що ми обходимо функції, маніпулюємо, модифікуємо, зберігаємося тощо. Це сутності, агрегати, колекції, класи кейсів. Іншим видом були б параметри "обслуговування". Це класи, які інкапсулюють бізнес-логіку, дозволяють спілкуватися із зовнішніми системами, забезпечують доступ до даних.

Ось загальний робочий процес моєї архітектури на прикладі. Припустимо, у нас є FriendsViewControllerсписок, який відображає список друзів користувача, і ми маємо можливість видалити з друзів. Я створюю у своєму FriendsServicesкласі метод під назвою:

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

де Friendмодель / доменний об’єкт (або він може бути просто Userоб’єктом, якщо вони мають подібні атрибути). Underhood цього методу розборів Friendдо NSDictionaryпараметрів JSON friend_id, name, surname, friend_request_idі так далі. Я завжди використовую бібліотеку Mantle для подібного шаблону і для мого шару моделі (розбір назад і вперед, управління вкладеними ієрархіями об'єктів у JSON тощо). Після розбору він викликає APIClient DELETEметод , щоб зробити фактичний запит REST і повертається Responseв RACSignalвикликає програмі ( FriendsViewControllerв нашому випадку) , щоб відобразити відповідне повідомлення для користувача або будь-який інший .

Якщо наш додаток дуже великий, ми повинні розділити свою логіку ще зрозуміліше. Наприклад, не завжди добре змішувати Repositoryабо моделювати логіку з Serviceоднією. Коли я описав свій підхід, я сказав, що removeFriendметод повинен бути в Serviceшарі, але якщо ми будемо більш педантичними, ми можемо помітити, що він краще належить Repository. Давайте згадаємо, що таке сховище. Ерік Еванс дав це точний опис у своїй книзі [DDD]:

Репозиторій представляє всі об'єкти певного типу у вигляді концептуального набору. Він діє як колекція, за винятком більш детальної можливості запиту.

Отже, a Repositoryпо суті є фасадом, який використовує семантику стилю колекції (Додати, оновити, видалити) для забезпечення доступу до даних / об’єктів. Ось чому , коли у вас є що - щось на кшталт: getFriendsList, getUserGroups, removeFriendви можете помістити його в Repository, оскільки збір подібної семантиці досить ясно тут. І такий код:

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

це безумовно логіка бізнесу, оскільки вона виходить за рамки основних CRUDоперацій і з'єднує два доменні об'єкти ( Friendі Request), тому її слід розміщувати в Serviceшарі. Також хочу зазначити: не створюйте зайвих абстракцій . Використовуйте всі ці підходи з розумом. Тому що, якщо ви будете переповнювати свою програму абстракціями, це збільшить її випадкову складність, а складність спричинить більше проблем у програмних системах, ніж будь-що інше

Я описую вам "старий" приклад Objective-C, але цей підхід може бути дуже легко адаптований для мови Swift з набагато більшими вдосконаленнями, оскільки він має більше корисних функцій та функціональний цукор. Дуже рекомендую використовувати цю бібліотеку: Moya . Це дозволяє створити більш елегантний APIClientшар (наш робочий коник, як ви пам’ятаєте). Тепер нашим APIClientпровайдером буде тип значень (enum) з розширеннями, що відповідають протоколам, та використовуючи відповідність зразків руйнування. Swift enums + узгодження шаблонів дозволяє нам створювати алгебраїчні типи даних, як у класичному функціональному програмуванні. Наші мікросервіси будуть використовувати цього вдосконаленого APIClientпостачальника, як у звичайному підході Objective-C. Для шару моделі замість Mantleвас можна використовувати бібліотеку ObjectMapperабо мені подобається використовувати більш елегантну та функціональну бібліотеку Argo .

Отже, я описав мій загальний архітектурний підхід, який можна пристосувати для будь-якого застосування. Звичайно, може бути набагато більше поліпшень. Раджу вам вивчити функціональне програмування, тому що ви можете отримати від цього багато користі, але не надто перешкоджайте цьому. Усунення надмірного, загального, глобального стану, що змінюється, створення непорушної моделі домену або створення чистих функцій без зовнішніх побічних ефектів, як правило, є хорошою практикою, і нова Swiftмова заохочує це. Але завжди пам’ятайте, що перевантаження вашого коду важкими чистими функціональними зразками, категорично-теоретичні підходи - погана ідея, оскільки інші розробники будуть читати та підтримувати ваш код, і вони можуть бути розчаровані чи страшніprismatic profunctorsі такі речі у вашій незмінній моделі. Те ж саме ReactiveCocoa: не вдавайте RACifyсвій код занадто багато , оскільки він може стати нечитабельним дуже швидко, особливо для новачків. Використовуйте його, коли це дійсно може спростити ваші цілі та логіку.

Так, read a lot, mix, experiment, and try to pick up the best from different architectural approaches. Це найкраща порада, яку я можу дати вам.


Також цікавий і ґрунтовний підхід. Дякую.
MainstreamDeveloper00

1
@darksider Як я вже писав у своїй відповіді: "` Я ніколи не використовую одинаків, Боже APIManager. Який би клас чи інші помилкові речі, тому що синглтон є анти-шаблоном, і в більшості випадків (крім рідкісних) - неправильне рішення. ". 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 Один раз `) у кожного контролера.
Олександр Караберов

14
Привіт @alexander. Чи є у вас приклади проектів на GitHub? Ви описуєте дуже цікавий підхід. Дякую. Але я новачок у розробці Objective-C. І мені важко зрозуміти деякі аспекти. Можливо, ви можете завантажити якийсь тестовий проект на GitHub і дати посилання?
Денис

1
Привіт @AlexanderKaraberov, я трохи розгублений щодо пояснення магазину, яке ви дали. Припустимо, у мене є 5 моделей, для кожного я маю 2 класи, в якій підтримується мережеве та інше кешування об'єктів. Тепер я повинен мати окремий клас Store для кожної моделі, яка викликає функцію мережевого та кеш-класу, або єдиний клас Store, який має всі функції для кожної моделі, тому контролер завжди отримує доступ до одного файлу для даних.
метеори

1
@icodebuster цей демонстраційний проект допомагає мені зрозуміти багато викладених тут концепцій: github.com/darthpelo/NetworkLayerExample

31

Відповідно до мети цього питання, я хотів би описати наш підхід до архітектури.

Архітектурний підхід

Архітектура нашого спільного застосування Іос стоїть на наступних моделей: Обслуговування шарів , MVVM , язування UI даних , Dependency Injection ; і парадигма функціонального реактивного програмування .

Ми можемо розрізати типовий для споживача додаток на такі логічні шари:

  • Асамблея
  • Модель
  • Послуги
  • Зберігання
  • Менеджери
  • Координатори
  • UI
  • Інфраструктура

Складальний шар - це точка завантаження нашого додатку. Він містить контейнер Dependency Injection та декларації об'єктів програми та їх залежностей. Цей шар також може містити конфігурацію програми (URL-адреси, ключі сторонніх служб тощо). Для цього ми використовуємо бібліотеку Тайфунів .

Модельний рівень містить класи доменних моделей, перевірки, відображення. Ми використовуємо бібліотеку Mantle для відображення наших моделей: вона підтримує серіалізацію / дезаріалізацію у JSONформат та NSManagedObjectмоделі. Для перевірки та представлення форм наших моделей ми використовуємо бібліотеки FXForms та FXModelValidation .

Службовий рівень оголошує послуги, які ми використовуємо для взаємодії із зовнішніми системами для того, щоб надсилати або отримувати дані, представлені в нашій моделі домену. Тому зазвичай ми маємо сервіси для зв'язку з API сервера (на особу), сервіси обміну повідомленнями (як PubNub ), служби зберігання даних (наприклад, Amazon S3) тощо. В основному сервіси обгортають об'єкти, надані SDK (наприклад, PubNub SDK) або реалізують власне спілкування логіка. Для загальних мереж ми використовуємо бібліотеку AFNetworking .

Мета шару зберігання - організувати локальне зберігання даних на пристрої. Для цього ми використовуємо Core Data або Realm (як плюси, так і мінуси. Рішення щодо використання базується на конкретних характеристиках). Для налаштування основних даних ми використовуємо бібліотеку MDMCoreData та купу класів - сховищ - (аналогічно сервісам), які забезпечують доступ до локального сховища для кожної особи. Для Realm ми просто використовуємо схожі сховища для доступу до місцевих сховищ.

Менеджерський шар - це місце, де живуть наші абстракції / обгортки.

У ролі менеджера можуть бути:

  • Менеджер облікових даних з його різними реалізаціями (брелок, NSDefaults, ...)
  • Поточний менеджер сесій, який вміє зберігати та надавати поточний сеанс користувача
  • Захоплення трубопроводу, який забезпечує доступ до медіа-пристроїв (відеозапис, аудіо, фотографування)
  • BLE Manager, який забезпечує доступ до Bluetooth та периферійних пристроїв
  • Geo Location Manager
  • ...

Отже, у ролі менеджера може виступати будь-який об’єкт, який реалізує логіку певного аспекту або проблеми, необхідні для роботи додатків.

Ми намагаємось уникати одинаків, але цей шар - це місце, де вони живуть, якщо вони знадобляться.

Шар координаторів забезпечує об'єкти, які залежать від об'єктів з інших шарів (Сервіс, Зберігання, Модель), щоб об'єднати їх логіку в одну послідовність роботи, необхідну для певного модуля (функція, екран, історія користувача або досвід користувача). Зазвичай це ланцюг асинхронних операцій і знає, як реагувати на випадки їх успіху та відмови. Як приклад можна представити функцію обміну повідомленнями та відповідний MessagingCoordinatorоб’єкт. Обробка операцій надсилання повідомлень може виглядати приблизно так:

  1. Підтвердження повідомлення (рівень моделі)
  2. Зберегти повідомлення локально (зберігання повідомлень)
  3. Завантажити вкладення повідомлення (послуга amazon s3)
  4. Оновіть статус повідомлення та вкладені URL-адреси та збережіть повідомлення на локальному рівні (зберігання повідомлень)
  5. Серіалізувати повідомлення у форматі JSON (шар моделі)
  6. Опублікувати повідомлення PubNub (послуга PubNub)
  7. Оновіть статус та атрибути повідомлення та збережіть його локально (зберігання повідомлень)

На кожному з вищезазначених етапів помилка обробляється відповідно.

Шар користувача складається з наступних підшарів:

  1. ViewModels
  2. ViewControllers
  3. Перегляди

Щоб уникнути масових контролерів перегляду, ми використовуємо шаблон MVVM та реалізуємо логіку, необхідну для представлення інтерфейсу користувача у ViewModels. ViewModel зазвичай має координаторів та менеджерів як залежних. ViewModels, що використовується ViewControllers та деякими видами переглядів (наприклад, комірки подання таблиці). Клей між ViewControllers та ViewModels - це шаблон прив'язки даних та команд. Для того, щоб зробити це клеєм, ми використовуємо бібліотеку ReactiveCocoa .

Ми також використовуємо ReactiveCocoa та його RACSignalконцепцію як інтерфейс та тип повертаючої вартості всіх координаторів, служб, методів зберігання. Це дозволяє нам здійснювати ланцюжок операцій, виконувати їх паралельно або послідовно та багато інших корисних речей, які надає ReactiveCocoa.

Ми намагаємось реалізувати нашу поведінку інтерфейсу декларативно. Прив’язка даних та автоматичний макет дуже допомагають досягти цієї мети.

Інфраструктурний шар містить усі помічники, розширення, утиліти, необхідні для роботи з додатком.


Цей підхід добре працює для нас і тих типів додатків, які ми зазвичай створюємо. Але ви повинні розуміти, що це лише суб'єктивний підхід, який слід адаптувати / змінити для конкретної мети команди.

Сподіваюся, це допоможе вам!

Також ви можете знайти додаткову інформацію про процес розробки iOS у цій публікації в блозі iOS Development як послуга


Почала сподобатися ця архітектура кілька місяців тому, дякую Алексу за те, що поділився нею! Я хотів би спробувати це з RxSwift найближчим часом!
інгахам

18

Оскільки всі програми для iOS різні, я думаю, що тут слід розглянути різні підходи, але я зазвичай йду таким чином:
Створіть центральний клас менеджера (синглтон) для обробки всіх запитів API (зазвичай називається APICommunicator), і кожен метод примірника - це виклик API . І є один центральний (недержавний) метод:

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

Для запису я використовую 2 основні бібліотеки / фреймворки, ReactiveCocoa та AFNetworking. ReactiveCocoa відмінно обробляє відповіді на асинхронізовану мережу, ви можете це зробити (sendNext:, sendError: тощо).
Цей метод викликає API, отримує результати та надсилає їх через RAC у "сировинному" форматі (наприклад, NSArray, що повертає AFNetworking).
Тоді такий спосіб, як getStuffList:названий вищевказаний метод, підписується на його сигнал, розбирає необроблені дані на об'єкти (щось подібне до Motis) і посилає об'єкти по черзі на абонент ( getStuffList:і подібні методи також повертають сигнал, на який може підписатися контролер ).
Підписаний контролер приймає об'єкти за допомогою subscribeNext:блоку і обробляє їх.

Я спробував багато способів у різних додатках, але цей працював найкраще з усіх, тому я використовував це в декількох додатках останнім часом, він підходить як для малих, так і для великих проектів, і це легко розширити і підтримувати, якщо щось потрібно змінити.
Сподіваюся, це допомагає, я хотів би почути думку інших щодо мого підходу, а може, як інші думають, що це може бути покращено.


2
Дякуємо за відповідь +1. Хороший підхід. Я залишаю питання. Можливо, у нас будуть ще деякі підходи від інших розробників.
MainstreamDeveloper00

1
Мені подобається варіація цього підходу - я використовую центральний менеджер API, який піклується про механіку спілкування з API. Однак я намагаюся зробити всю функціональність об'єктами моєї моделі. Моделі надають такі методи, як + (void)getAllUsersWithSuccess:(void(^)(NSArray*))success failure:(void(^)(NSError*))failure;і - (void)postWithSuccess:(void(^)(instancetype))success failure:(void(^)(NSError*))failure;які виконують необхідні препарати, а потім звертаються до менеджера API.
jsadler

1
Цей підхід є простим, але в міру збільшення кількості API, підтримувати менеджер API одиночних програм стає важче. І кожен новий доданий API буде стосуватися менеджера, незалежно від того, до якого модуля цей API належить. Спробуйте використовувати github.com/kevin0571/STNetTaskQueue для управління запитами API.
Кевін

Окрім суті, чому ви рекламуєте свою бібліотеку, яка є якомога далі від мого рішення та набагато складнішою, я спробував такий підхід у незліченних проектах, як малих, так і великих, як згадувалося, і я використовую саме його те саме з того часу, як я написав цю відповідь. З розумними умовами іменування це зовсім не важко підтримувати.
Рікі

8

У своїй ситуації я зазвичай використовую бібліотеку ResKit для налаштування мережевого рівня. Він забезпечує простий у використанні синтаксичний розбір. Це зменшує мої зусилля щодо налаштування карти для різних відповідей та інших матеріалів.

Я додаю лише якийсь код для автоматичного налаштування карти. Я визначаю базовий клас для своїх моделей (не протокол через багато коду, щоб перевірити, чи реалізований якийсь метод, чи ні, і менше коду в самих моделях):

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

Відносини - це об'єкти, які представляють вкладені об'єкти у відповідь:

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

ВідносиниОб'єкт.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

Тоді я налаштовую відображення для RestKit так:

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

Деякі приклади реалізації MappableEntry:

User.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

Користувач.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

Тепер про завершення запитів:

У мене є файл заголовка з визначенням блоків, щоб зменшити довжину рядка у всіх класах APIRequest:

APICallbacks.h

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

І Приклад мого класу APIRequest, який я використовую:

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

І все, що вам потрібно зробити в коді, просто ініціалізувати об’єкт API і викликати його, коли вам це потрібно:

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

Мій код не ідеальний, але його легко встановити один раз і використовувати для різних проектів. Якщо комусь це цікаво, mb я міг би витратити трохи часу і зробити універсальне рішення для нього десь на GitHub та CocoaPods.


7

На мій погляд, вся архітектура програмного забезпечення керується потребою. Якщо це для навчальних чи особистих цілей, тоді вирішіть основну мету і майте, що керує архітектурою. Якщо це робота в оренді, то необхідність бізнесу є найважливішою. Хитрість полягає в тому, щоб не дозволяти блискучим речам відволікати вас від реальних потреб. Мені це важко зробити. Завжди з’являються нові блискучі речі в цьому бізнесі, і багато з них не є корисними, але ви не завжди можете це сказати наперед. Зосередьтеся на необхідності та будьте готові відмовитися від поганого вибору, якщо зможете.

Наприклад, нещодавно я зробив швидкий прототип програми обміну фотографіями для місцевого бізнесу. Оскільки бізнес повинен був зробити щось швидке і брудне, архітектура виявилася деяким кодом iOS для появи камери та деяким мережевим кодом, приєднаним до кнопки "Надіслати", яка завантажила зображення в магазин S3 і записала в домен SimpleDB. Код був тривіальним, мінімальна вартість, і клієнт має масштабовану колекцію фотографій, доступну через Інтернет за допомогою REST-дзвінків. Дешевий і тупий, додаток мав багато недоліків і заважав би користувальницькому інтерфейсу при нагоді, але було б марно зробити більше для прототипу, і це дозволяє їм розгортатись до своїх співробітників і генерувати тисячі тестових зображень легко без продуктивності та масштабованості проблеми. Хитра архітектура, але вона відповідає потребі і коштує ідеально.

Інший проект передбачав впровадження локальної захищеної бази даних, яка синхронізується з системою компанії у фоновому режимі, коли мережа доступна. Я створив фоновий синхронізатор, який використовував RestKit, як здавалося, у ньому все, що мені потрібно. Але мені довелося написати стільки спеціального коду для RestKit, щоб мати справу з ідіосинкратичним JSON, що я міг би зробити це швидше, написавши власний JSON для перетворень CoreData. Однак замовник хотів принести цей додаток в дім, і я відчув, що RestKit буде схожий на рамки, які вони використовували на інших платформах. Я чекаю, чи не вдалося це рішення.

Знову ж таки, питання для мене полягає в тому, щоб зосередити увагу на необхідності і нехай це визначає архітектуру. Я намагаюся, як пекло, уникати використання сторонніх пакетів, оскільки вони приносять витрати, які з’являються лише після того, як додаток буде деякий час у полі. Я намагаюся уникати ієрархій класів, оскільки вони рідко окупаються. Якщо я можу щось написати за розумний проміжок часу замість того, щоб прийняти пакет, який не ідеально підходить, я це роблю. Мій код добре структурований для налагодження і відповідним чином коментується, але сторонні пакети рідко бувають. Зважаючи на це, я вважаю, що мережа AF занадто корисна для того, щоб ігнорувати та добре структурувати, добре коментувати та підтримувати, і я її дуже використовую! RestKit охоплює безліч поширених випадків, але я відчуваю, що я боровся, коли використовую його, і більшість джерел даних, з якими я стикаюся, переповнені химерностями та проблемами, які найкраще впорядковуються за допомогою спеціального коду. У своїх останніх кількох додатках я просто використовую вбудовані в JSON перетворювачі та записую кілька корисних методів.

Я завжди використовую схему, щоб відключити мережеві дзвінки від основної нитки. Останні 4-5 додатків, які я зробив, створили задачу фонового таймера за допомогою dispatch_source_create, яка прокидається так часто і виконує завдання мережі. Вам потрібно виконати деякі роботи з безпеки потоку і переконатися, що код, що модифікує інтерфейс, надсилається до основного потоку. Це також допомагає зробити ваш борт / ініціалізацію таким чином, щоб користувач не відчував себе обтяженим або затриманим. Поки що це працює досить добре. Я пропоную вивчити ці речі.

Нарешті, я думаю, що, коли ми працюємо більше і в міру розвитку ОС, ми прагнемо розробляти кращі рішення. Минуло багато років, щоб переконатись, що я повинен слідувати зразкам і конструкціям, які інші люди стверджують, що є обов'язковими. Якщо я працюю в контексті, коли це є частиною місцевої релігії, ага, я маю на увазі найкращу інженерну практику відомства, то я дотримуюсь звичаїв до листа, саме за це вони мені платять. Але я рідко зустрічаю, що дотримання старих конструкцій та моделей є оптимальним рішенням. Я завжди намагаюся дивитись на рішення через призму бізнес-потреб і будувати архітектуру, щоб відповідати цьому та зберігати речі настільки просто, наскільки вони можуть бути. Коли я відчуваю, що там недостатньо, але все працює правильно, тоді я на вірному шляху.


4

Я використовую підхід, який я отримав звідси: https://github.com/Constantine-Fry/Foursquare-API-v2 . Я переписав цю бібліотеку в Swift, і ви можете побачити архітектурний підхід з цих частин коду:

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}}
}

В основному, існує підклас NSOperation, який робить NSURLRequest, аналізує JSON-відповідь і додає блок чергового виклику з результатом до черги. Основний клас API будує NSURLRequest, ініціалізує цей підклас NSOperation і додає його до черги.


3

Ми використовуємо кілька підходів залежно від ситуації. Для більшості речей AFNetworking - це найпростіший і надійний підхід, в якому ви можете встановлювати заголовки, завантажувати багаточастинні дані, використовувати GET, POST, PUT & DELETE, а для UIKit існує маса додаткових категорій, які дозволяють, наприклад, встановити зображення з URL-адреса. У складному додатку з великою кількістю дзвінків ми іноді конспектуємо це до власного зручного методу, який би був на кшталт:

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

Є кілька ситуацій, коли AFNetworking не підходить, однак, наприклад, коли ви створюєте фреймворк або інший компонент бібліотеки, оскільки AFNetworking вже може бути в іншій кодовій базі. У цій ситуації ви б використовували NSMutableURLRequest або вбудований, якщо ви здійснюєте один дзвінок або абстрагуєтесь на клас запиту / відповіді.


Для мене це найкраща і найясніша відповідь, ура. "Це все просто". @martin, особисто ми просто використовуємо NSMutableURLRequest весь час; чи є реальна причина використовувати AFNetworking?
Fattie

AFNetworking - це просто зручно. Для мене блоки успіху і провалу роблять це вартим, оскільки це полегшує управління кодом. Я погоджуюся, що іноді це загальний перевибір.
Мартін

Чудова точка на блоках, дякую за це. Я здогадуюсь, специфіка цього все зміниться зі Свіфтом.
Fattie

2

Я уникаю синглів під час проектування моїх додатків. Вони є типовим для багатьох людей, але я думаю, що ви можете знайти більш елегантні рішення деінде. Зазвичай те, що я роблю, - це складання моїх сутностей у CoreData, а потім розміщення коду REST у категорії NSManagedObject. Якщо, наприклад, я хотів створити та розмістити нового користувача, я зробив би це:

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

Я використовую RESTKit для відображення об'єкта та ініціалізую його при запуску. Я вважаю, що маршрутизація всіх ваших дзвінків через одиночну кнопку є марною тратою часу і додає багато котлів, які не потрібні.

У 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];
}

У 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];
}

Навіщо додавати додаткові класи помічників, коли ви можете розширити функціональність загального базового класу на категорії?

Якщо вас цікавить більш детальна інформація про моє рішення, дайте мені знати. Я раді поділитися.


3
Напевно було б цікаво прочитати про цей підхід детальніше в дописі блогу.
Данял Айтекін

0

Спробуйте https://github.com/kevin0571/STNetTaskQueue

Створення запитів API у відокремлених класах.

STNetTaskQueue буде мати справу з потоком та делегуванням / зворотним дзвоном.

Розширюється для різних протоколів.


0

З чисто класового дизайну, у вас зазвичай буде щось подібне:

  • Ваші контролери перегляду, що управляють одним або кількома переглядами
  • Клас моделі даних - це дійсно залежить від того, скільки реальних осіб, з якими ви маєте справу, і як вони пов'язані.

    Наприклад, якщо у вас є масив елементів, який повинен відображатися в чотирьох різних представленнях (список, діаграма, графік тощо), у вас буде один клас моделі даних для списку елементів, і ще один для елемента. Список класу пункту буде розділений чотири контролерів зору - все діти контролера панелі вкладок або контролера нав.

    Класи моделей даних стануть у нагоді не лише у відображенні даних, а й у їх серіалізації, де кожен із них може викрити свій власний формат серіалізації за допомогою методів експорту JSON / XML / CSV (чи будь-чого іншого).

  • Важливо розуміти, що вам також потрібні класи конструктора запитів API, які відображаються безпосередньо з кінцевими точками API REST API. Скажімо, у вас є API, який записує користувача в систему - так ваш клас конструктора API для входу створить корисний навантаження POST JSON для API для входу. В іншому прикладі, клас конструктора запитів API для списку API елементів каталогу створить GET рядок запиту для відповідних api та запустить REST GET-запит.

    Ці класи конструкторів запитів API зазвичай отримують дані від контролерів перегляду, а також передають ті самі дані назад для перегляду контролерів для оновлення / інших операцій інтерфейсу. Потім контролери перегляду вирішать, як оновити об'єкти Data Data з цими даними.

  • Нарешті, серце клієнта REST - клас вибору даних API, який не звертає уваги на всі види запитів API, які робить ваше додаток. Цей клас, швидше за все, буде синглтоном, але, як зазначали інші, він не повинен бути синглтоном.

    Зауважте, що посилання є лише типовою реалізацією і не враховує такі сценарії, як сеанс, файли cookie тощо, але цього достатньо, щоб ви йшли без використання сторонніх фреймворків.


0

На це запитання вже є багато відмінних та обширних відповідей, але я відчуваю, що мушу його згадати, оскільки ще ніхто не має.

Аламофір для Свіфта. https://github.com/Alamofire/Alamofire

Він створений тими ж людьми, що і AFNetworking, але більш безпосередньо розробляється з урахуванням Swift.


0

Я думаю, що зараз середній проект використовує архітектуру MVVM, а Big Project використовують архітектуру VIPER і намагаються досягти цього

  • Протоколо орієнтоване програмування
  • Шаблони дизайну програмного забезпечення
  • Принцип ПРОДАЖУ
  • Загальне програмування
  • Не повторюй себе (ДРУГИЙ)

І архітектурні підходи до створення мережевих додатків iOS (клієнти REST)

Піклування про роздільний чистий і читабельний код уникає дублювання:

import Foundation
enum DataResponseError: Error {
    case network
    case decoding

    var reason: String {
        switch self {
        case .network:
            return "An error occurred while fetching data"
        case .decoding:
            return "An error occurred while decoding data"
        }
    }
}

extension HTTPURLResponse {
    var hasSuccessStatusCode: Bool {
        return 200...299 ~= statusCode
    }
}

enum Result<T, U: Error> {
    case success(T)
    case failure(U)
}

інверсія залежності

 protocol NHDataProvider {
        func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL, completion: @escaping (Result<Codable, DataResponseError>) -> Void)
    }

Основна відповідальність:

  final class NHClientHTTPNetworking : NHDataProvider {

        let session: URLSession

        init(session: URLSession = URLSession.shared) {
            self.session = session
        }

        func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL,
                             completion: @escaping (Result<Codable, DataResponseError>) -> Void) {
            let urlRequest = URLRequest(url: url)
            session.dataTask(with: urlRequest, completionHandler: { data, response, error in
                guard
                    let httpResponse = response as? HTTPURLResponse,
                    httpResponse.hasSuccessStatusCode,
                    let data = data
                    else {
                        completion(Result.failure(DataResponseError.network))
                        return
                }
                guard let decodedResponse = try? JSONDecoder().decode(Model.self, from: data) else {
                    completion(Result.failure(DataResponseError.decoding))
                    return
                }
                completion(Result.success(decodedResponse))
            }).resume()
        }
    }

Тут ви знайдете архітектуру GitHub MVVM з рештою API Swift Project


0

У інженерії програмного забезпечення для мобільних пристроїв найбільш широко використовуються шаблони Clean Architecture + MVVM та Redux.

Чиста архітектура + MVVM складаються з 3 шарів: Домен, Презентація, Шари даних. Де рівень шару презентації та сховища даних залежать від шару домену:

Presentation Layer -> Domain Layer <- Data Repositories Layer

І презентаційний шар складається з ViewModels і Views (MVVM):

Presentation Layer (MVVM) = ViewModels + Views
Domain Layer = Entities + Use Cases + Repositories Interfaces
Data Repositories Layer = Repositories Implementations + API (Network) + Persistence DB

У цій статті є більш детальний опис чистої архітектури + MVVM https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.