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 также является анти-паттерном, и в большинстве случаев (кроме редких) это неправильное решение. Синглтон следует рассматривать только в том случае, если выполнены все три из следующих критериев:
- Право собственности на один экземпляр не может быть разумно присвоено;
- Ленивая инициализация желательна;
- Глобальный доступ не предусмотрен иным образом.
В нашем случае владение отдельным экземпляром не является проблемой, и нам также не нужен глобальный доступ после того, как мы разделили нашего 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
. Это лучший совет, который я могу вам дать.