Лучшие архитектурные подходы для создания сетевых приложений 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. Надеюсь на подробное объяснение от опытных разработчиков.

MainstreamDeveloper00
источник
14
Разве это не вопрос списка покупок? У меня просто был вопрос, за который проголосовали в ад и закрыли, потому что было заявлено, что «что является лучшим», типа вопросов вызывают слишком много неконструктивных дебатов. Что делает этот вопрос о списке покупок хорошим вопросом, достойным голосов и щедрости, в то время как другие закрываются?
Элвин Томпсон
1
Обычно сетевая логика входит в контроллер, который изменяет модельный объект и уведомляет любого делегата или наблюдателей.
Унылое
1
Очень интересные вопросы и ответы. После 4 лет iOS-кодирования и попыток найти самый красивый способ добавить сетевой слой в приложение. Какой класс должен отвечать за управление сетевым запросом? Ответы ниже действительно уместны. Спасибо
darksider
@JoeBlow это не правда. Индустрия мобильных приложений все еще во многом зависит от взаимодействия между сервером и клиентом.
scord

Ответы:

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это не ORM или база данных, а диспетчер графов объектов с хорошим постоянством. Таким образом, очень часто это Core Dataможет быть слишком тяжело для ваших нужд, и вы можете посмотреть на новые решения, такие как Realm и Couchbase Lite , или создать свой собственный облегченный слой отображения / сохранения объектов, основанный на необработанном SQLite или LevelDB, Также я советую вам ознакомиться с Domain Driven Design и 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 и 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 . Если у вас есть кеш: создайте его как отдельный слой / службу (не следует смешивать бизнес-логику с кешированием). Зачем? Потому что правильный кеширующий слой может быть довольно сложным с собственными ошибками. Люди реализуют сложную логику, чтобы получить правильное и предсказуемое кэширование, например, моноидальное кэширование с проекциями, основанными на профункторах. Вы можете прочитать об этой прекрасной библиотеке под названием Карлос, чтобы понять больше. И не забывайте, что Core Data действительно может помочь вам со всеми проблемами кэширования и позволит вам писать меньше логики. Кроме того, если у вас есть некоторая логика между NSManagedObjectContextмоделями запросов к серверу, вы можете использоватьШаблон репозитория , который отделяет логику, которая извлекает данные, и сопоставляет ее с моделью сущностей от бизнес-логики, действующей на модели. Поэтому я советую использовать шаблон репозитория, даже если у вас есть архитектура на основе Core Data. Repository тазов абстрактные вещи, как NSFetchRequest, NSEntityDescription, NSPredicateи так далее для простых методов , таких как getили put.

После всех этих действий на сервисном уровне вызывающая сторона (контроллер представления) может выполнить некоторые сложные асинхронные действия с ответом: манипулирование сигналом, сцепление, отображение и т. Д. С помощью ReactiveCocoaпримитивов или просто подписаться на него и показать результаты в представлении. , Я впрыснуть с Injection Dependency во всех этих службах классов моих APIClient, которая переведет конкретный вызов службы в соответствующем GET, POST, PUT, DELETEи т.д. запрос на REST конечной точку. В этом случае APIClientнеявно передается всем контроллерам, вы можете сделать это явно с параметризацией по APIClientклассам обслуживания. Это может иметь смысл, если вы хотите использовать различные настройкиAPIClientдля определенных классов обслуживания, но если по каким-то причинам вам не нужны дополнительные копии или вы уверены, что всегда будете использовать один конкретный экземпляр (без настроек) APIClient- сделайте его одиночным, но НЕ делайте, пожалуйста, НЕ Занимаюсь обслуживанием в качестве одиночек.

Затем каждый контроллер представления снова с помощью DI вводит необходимый ему класс обслуживания, вызывает соответствующие методы обслуживания и объединяет их результаты с логикой пользовательского интерфейса. Для внедрения зависимостей мне нравится использовать BloodMagic или более мощный фреймворк Typhoon . Я никогда не использую одиночные игры, уроки Бога APIManagerWhateverили другие неправильные вещи. Потому что если вы звоните своему классу WhateverManager, это указывает на то, что вы не знаете его цели, и это плохой выбор дизайна . Singletons также является анти-паттерном, и в большинстве случаев (кроме редких) это неправильное решение. Синглтон следует рассматривать только в том случае, если выполнены все три из следующих критериев:

  1. Право собственности на один экземпляр не может быть разумно присвоено;
  2. Ленивая инициализация желательна;
  3. Глобальный доступ не предусмотрен иным образом.

В нашем случае владение отдельным экземпляром не является проблемой, и нам также не нужен глобальный доступ после того, как мы разделили нашего God Manager на сервисы, потому что теперь только один или несколько выделенных контроллеров нуждаются в конкретной услуге (например, 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по сути является фасадом, который использует семантику стиля Collection (Add, Update, Remove) для предоставления доступа к данным / объектам. Вот почему , когда у вас есть что - то вроде: getFriendsList, getUserGroups, removeFriendвы можете поместить его в Repository, поскольку сбор подобной семантике довольно ясно здесь. И код как:

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

это определенно бизнес-логика, потому что она выходит за рамки базовых CRUDопераций и соединяет два объекта домена ( Friendи Request), поэтому она должна быть размещена на Serviceуровне. Также хочу заметить: не создавайте ненужных абстракций . Используйте все эти подходы с умом. Потому что если вы переполните свое приложение абстракциями, это увеличит его случайную сложность, и сложность вызывает больше проблем в программных системах, чем что-либо еще

Я описываю вам «старый» пример Objective-C, но этот подход может быть очень легко адаптирован для языка Swift с гораздо большим количеством улучшений, потому что он имеет больше полезных функций и функциональных возможностей. Я настоятельно рекомендую использовать эту библиотеку: Моя . Позволяет создать более элегантный APIClientслой (наша рабочая лошадка, как вы помните). Теперь нашим APIClientпровайдером будет тип значения (enum) с расширениями, соответствующими протоколам и использующими сопоставление с образцом деструктуризации. Быстрое перечисление + сопоставление с образцом позволяет нам создавать алгебраические типы данных, как в классическом функциональном программировании. Наши микросервисы будут использовать этого улучшенного 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 Как я уже писал в своем ответе: «` Я никогда не использую одиночек, класс Бог APIManagerWhatever или другие неправильные вещи, потому что синглтон является анти-модель, и в большинстве случаев ( за исключением редких) неправильное решение. ". 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
  • инфраструктура

Сборочный слой является начальной точкой нашего приложения. Он содержит контейнер внедрения зависимостей и объявления объектов приложения и их зависимостей. Этот слой также может содержать конфигурацию приложения (URL, сторонние сервисные ключи и т. Д.). Для этого мы используем библиотеку Тайфун .

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

Сервисный уровень объявляет сервисы, которые мы используем для взаимодействия с внешними системами с целью отправки или получения данных, которые представлены в нашей модели предметной области. Поэтому обычно у нас есть службы для связи с серверными API-интерфейсами (для каждой сущности), службами обмена сообщениями (например, PubNub ), службами хранения (например, Amazon S3) и т. Д. В основном службы обертывают объекты, предоставляемые SDK (например, PubNub SDK), или реализуют свои собственные средства связи. логика. Для общих сетей мы используем библиотеку AFNetworking .

Цель уровня хранения - организовать локальное хранение данных на устройстве. Для этого мы используем Core Data или Realm (у обоих есть свои плюсы и минусы, решение о том, что использовать, основано на конкретных спецификациях). Для настройки Core Data мы используем библиотеку MDMCoreData и несколько классов - хранилищ (аналогично сервисам), которые предоставляют доступ к локальному хранилищу для каждого объекта. Для Realm мы просто используем подобные хранилища, чтобы иметь доступ к локальному хранилищу.

Слой менеджеров - это место, где живут наши абстракции / оболочки.

В роли менеджера может быть:

  • Диспетчер учетных данных с различными реализациями (связка ключей, NSDefaults, ...)
  • Менеджер текущего сеанса, который знает, как сохранить и предоставить текущий сеанс пользователя
  • Capture Pipeline, обеспечивающий доступ к мультимедийным устройствам (запись видео, аудио, фотосъемка)
  • BLE Manager, обеспечивающий доступ к службам и периферийным устройствам Bluetooth
  • Geo Location Manager
  • ...

Таким образом, в роли менеджера может выступать любой объект, реализующий логику определенного аспекта или задачи, необходимую для работы приложения.

Мы стараемся избегать синглетонов, но этот слой - место, где они живут, если они необходимы.

Слой Coordinators предоставляет объекты, которые зависят от объектов других слоев (Service, Storage, Model), чтобы объединить их логику в одну последовательность работы, необходимую для определенного модуля (функция, экран, пользовательская история или пользовательский опыт). Обычно он объединяет асинхронные операции и знает, как реагировать на их успехи и неудачи. В качестве примера вы можете представить функцию обмена сообщениями и соответствующий MessagingCoordinatorобъект. Обработка операции отправки сообщения может выглядеть так:

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

На каждом из вышеперечисленных шагов ошибка обрабатывается соответственно.

Слой пользовательского интерфейса состоит из следующих подслоев:

  1. ViewModels
  2. ViewControllers
  3. Просмотры

Чтобы избежать Massive View Controllers, мы используем шаблон MVVM и реализуем логику, необходимую для представления пользовательского интерфейса во ViewModels. ViewModel обычно имеет координаторов и менеджеров в качестве зависимостей. ViewModels, используемые ViewControllers и некоторыми видами представлений (например, ячейки табличного представления). Склеивание между ViewControllers и ViewModels - это привязка данных и шаблон команд. Для того, чтобы получить этот клей, мы используем библиотеку ReactiveCocoa .

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

Мы пытаемся реализовать наше поведение пользовательского интерфейса декларативным способом. Привязка данных и автоматическое расположение очень помогают в достижении этой цели.

Уровень инфраструктуры содержит все помощники, расширения, утилиты, необходимые для работы приложения.


Этот подход хорошо работает для нас и тех типов приложений, которые мы обычно создаем. Но вы должны понимать, что это всего лишь субъективный подход, который следует адаптировать / изменить для целей конкретной команды.

Надеюсь, что это поможет вам!

Также вы можете найти более подробную информацию о процессе разработки iOS в этом блоге. IOS Development as a Service

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

Поскольку все приложения для iOS различны, я думаю, что здесь есть разные подходы, но я обычно так
и делаю: создаю класс центрального менеджера (singleton) для обработки всех запросов API (обычно называемых APICommunicator), и каждый метод экземпляра является вызовом API. , И есть один центральный (не публичный) метод:

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

Для записи я использую 2 основных библиотеки / фреймворка, ReactiveCocoa и AFNetworking. ReactiveCocoa отлично справляется с асинхронными сетевыми ответами, что вы можете сделать (sendNext :, sendError: и т. Д.).
Этот метод вызывает API, получает результаты и отправляет их через RAC в «сыром» формате (например, NSArray, который возвращает AFNetworking).
Затем метод, getStuffList:который вызвал вышеуказанный метод, подписывается на его сигнал, анализирует необработанные данные на объекты (с чем-то вроде Motis) и отправляет объекты один за другим вызывающей стороне ( getStuffList:и аналогичные методы также возвращают сигнал, на который контроллер может подписаться ).
Подписанный контроллер получает объекты по subscribeNext:блоку и обрабатывает их.

Я пробовал много способов в разных приложениях, но этот работал лучше всего из всех, поэтому я недавно использовал его в нескольких приложениях, он подходит как для небольших, так и для больших проектов, и его легко расширять и поддерживать, если что-то нужно изменить.
Надеюсь, это поможет, я хотел бы услышать мнение других о моем подходе и, возможно, как другие думают, что это может быть улучшено.

Rickye
источник
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

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

Затем я настраиваю отображение для 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

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

Мой код не идеален, но его легко установить один раз и использовать для разных проектов. Если это кому-то интересно, я бы мог потратить некоторое время и сделать для него универсальное решение где-нибудь на GitHub и CocoaPods.

Андрей Черкашин
источник
7

На мой взгляд, вся архитектура программного обеспечения определяется необходимостью. Если это для обучения или личных целей, то определите основную цель и задайте ей архитектуру. Если это работа по найму, то бизнес-необходимость имеет первостепенное значение. Хитрость заключается в том, чтобы не позволить блестящим вещам отвлечь вас от реальных потребностей. Мне трудно это сделать. В этом бизнесе всегда появляются новые блестящие вещи, и многие из них бесполезны, но вы не всегда можете сказать это заранее. Сосредоточьтесь на необходимости и будьте готовы отказаться от неправильного выбора, если можете.

Например, недавно я сделал быстрый прототип приложения для обмена фотографиями для местного бизнеса. Поскольку бизнес-необходимость заключалась в том, чтобы сделать что-то быстрое и грязное, архитектура в конечном итоге представляла собой некоторый код iOS для вызова камеры и некоторый сетевой код, присоединенный к кнопке отправки, которая загружала изображение в хранилище S3 и записывала его в домен SimpleDB. Код был тривиальным, а стоимость минимальной, и у клиента есть масштабируемая коллекция фотографий, доступная через Интернет с помощью вызовов REST. Дешевое и глупое, приложение имело много недостатков и иногда блокировало пользовательский интерфейс, но было бы бесполезно делать больше для прототипа, и это позволяло бы им развертывать свои кадры и легко генерировать тысячи тестовых изображений без производительности или масштабируемости. проблемы. Дрянная архитектура, но она подходит по необходимости и стоит отлично.

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

Опять же, проблема для меня заключается в том, чтобы сосредоточиться на необходимости и позволить этому определять архитектуру. Я стараюсь изо всех сил избегать использования сторонних пакетов, поскольку они приносят затраты, которые появляются только после того, как приложение находится в поле некоторое время. Я стараюсь избегать создания иерархий классов, поскольку они редко окупаются. Если я могу написать что-то в разумные сроки вместо того, чтобы принять пакет, который не подходит идеально, тогда я сделаю это. Мой код хорошо структурирован для отладки и соответствующим образом прокомментирован, но сторонние пакеты редко встречаются. С учетом сказанного, я считаю, что AF Networking слишком полезен, чтобы его игнорировать, хорошо структурировать, хорошо комментировать и поддерживать, и я его часто использую! 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 и добавляет его в очередь.

Bzz
источник
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?
Толстяк
AFNetworking просто удобно на самом деле. Для меня блоки успеха и неудачи делают все возможное, поскольку это облегчает управление кодом. Я согласен с тем, что иногда это просто перебор.
Мартин
Отличная точка на блоках, спасибо за это. Я предполагаю, что специфика этого все изменится со Свифтом.
Толстяк
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 будет работать с потоками и делегировать / обратный вызов.

Выдвижной для разных протоколов.

Kevin
источник
0

С чисто классовой точки зрения у вас обычно будет что-то вроде этого:

  • Ваши контроллеры представления, управляющие одним или несколькими представлениями
  • Класс модели данных - это действительно зависит от того, сколько реальных сущностей вы имеете дело, и как они связаны.

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

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

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

    Эти классы построителя запросов API обычно получают данные от контроллеров представления, а также передают те же данные обратно в контроллеры представления для обновления пользовательского интерфейса / других операций. Затем контроллеры представлений решат, как обновить объекты модели данных этими данными.

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

    Обратите внимание, что ссылка является просто типичной реализацией и не принимает во внимание сценарии, такие как сеанс, файлы cookie и т. Д., Но этого достаточно, чтобы начать работу без использования сторонних сред.

Нирав Бхатт
источник
0

На этот вопрос уже есть много отличных и обширных ответов, но я чувствую, что должен упомянуть об этом, потому что никто больше не имеет.

Alamofire для Swift. https://github.com/Alamofire/Alamofire

Он создан теми же людьми, что и AFNetworking, но более непосредственно разработан с учетом Swift.

matt.writes.code
источник
0

Я думаю, что сейчас средний проект использует архитектуру MVVM, а Большой проект использует архитектуру 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

В разработке мобильного программного обеспечения наиболее широко используются шаблоны «Чистая архитектура» + «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

Олег Кудинов
источник