лучшие архитектурные подходы для создания сетевых приложений ios (клиенты rest)

<p>я разработчик ios с некоторым опытом, и этот вопрос мне действительно интересен. я видел много разных ресурсов и материалов на эту тему, но, тем не менее, я все еще в замешательстве. какая архитектура лучше всего подходит для сетевого приложения ios? я имею в виду базовую абстрактную среду, шаблоны, которые подойдут каждому сетевому приложению, будь то небольшое приложение с несколькими запросами к серверу или сложный rest клиент. apple рекомендует использовать <code>mvc</code> в качестве базового архитектурного подхода для всех приложений ios, но ни <code>mvc</code>, ни более современные шаблоны <code>mvvm</code> не объясняют, куда поместить сетевой логический код и как его организовать в целом.</p><p>нужно ли разрабатывать что-то вроде <code>mvcs</code> (<code>s</code> для <code>service</code>) и в этот слой <code>service</code> поместить все запросы <code>api</code> и другую сетевую логику, что в перспективе может быть действительно сложным? после некоторых исследований я нашел два основных подхода к этому. <a href="https://stackoverflow.com/questions/4810289/best-architecture-for-an-ios-application-that-makes-many-network-requests">здесь</a> было рекомендовано создавать отдельный класс для каждого сетевого запроса к веб-службе <code>api</code> (например, класс <code>loginrequest</code> или класс <code>postcommentrequest</code> и т.д.), который все наследуется от абстрактного класса базового запроса <code>abstractbaserequest</code> и в дополнение к создайте некоторый глобальный сетевой менеджер, который инкапсулирует общий сетевой код и другие предпочтения (это может быть настройка <code>afnetworking</code> или настройка <code>restkit</code>, если у нас сложные сопоставления объектов и постоянство, или даже собственная реализация сетевого взаимодействия со стандартным api). но такой подход кажется мне непосильным. другой подход заключается в том, чтобы иметь какой-то одноэлементный диспетчер <code>api</code> или класс менеджера, как в первом подходе, <b>но нет</b> создавать классы для каждого запроса и вместо этого инкапсулировать каждый запрос как открытый метод экземпляра этого класса менеджера, например: методы <code>fetchcontacts</code>, <code>loginuser</code> и т. д. итак, каков наилучший и правильный путь? есть ли другие интересные подходы, которые я еще не знаю?</p><p>и должен ли я создать еще один слой для всех этих сетевых вещей, таких как слой <code>service</code>, или <code>networkprovider</code>, или что-нибудь еще поверх моей архитектуры <code>mvc</code>, или этот слой должен быть интегрирован (внедрен) в существующие слои <code>mvc</code>, например. <code>model</code>?</p><p>я знаю, что существуют красивые подходы, или как тогда такие мобильные монстры, как клиент facebook или клиент linkedin, справляются с экспоненциально растущей сложностью сетевой логики?</p><p>я знаю, что нет точного и формального ответа на проблему. <b>цель этого вопроса - собрать наиболее интересные подходы от опытных разработчиков ios.</b>, лучший предложенный подход будет помечен как принятый и награжден заслуженной репутацией, за других проголосуют. это в основном теоретический и исследовательский вопрос. я хочу понять базовый, абстрактный и правильный архитектурный подход для сетевых приложений в ios. надеюсь на подробное объяснение от опытных разработчиков.</p>

<p><code>i want to understand basic, abstract and correct architectural approach for networking applications in ios</code>: <em> нет </em> "лучшего" или "наиболее правильного" подхода для построения архитектуры приложения. это <em> очень </em> творческая работа. вы всегда должны выбирать наиболее простую и расширяемую архитектуру, которая будет понятна любому разработчику, начинающему работать над вашим проектом, или другим разработчикам в вашей команде, но я согласен, что могут быть "хорошие" и "плохие" "архитектура.</p><p>вы сказали: <code>collect the most interesting approaches from experienced ios developers</code>, я не думаю, что мой подход является наиболее интересным или правильным, но я использовал его в нескольких проектах и доволен им. это гибридный подход из тех, что вы упомянули выше, а также с улучшениями из моих собственных исследований. меня интересуют проблемы построения подходов, которые сочетают в себе несколько хорошо известных шаблонов и идиом. я думаю, что многие <a href="http://martinfowler.com/books/eaa.html">корпоративные модели фаулера</a> могут быть успешно применены к мобильным приложениям. вот список наиболее интересных из них, которые мы можем применить для создания архитектуры приложения для ios (<em> на мой взгляд </em>): <a href="http://martinfowler.com/eaacatalog/servicelayer.html">service layer</a> , - <a href="http://martinfowler.com/eaacatalog/unitofwork.html">единица работы</a> , <a href="http://martinfowler.com/eaacatalog/remotefacade.html">удаленный фасад</a> , <a href="http://martinfowler.com/eaacatalog/datatransferobject.html">объект передачи данных</a> , <a href="http://martinfowler.com/eaacatalog/gateway.html">шлюз</a> , <a href="http://martinfowler.com/eaacatalog/layersupertype.html">супертип слоя</a> , - <a href="http://martinfowler.com/eaacatalog/specialcase.html">особый случай</a> , <a href="http://martinfowler.com/eaacatalog/domainmodel.html">модель предметной области</a> . вы всегда должны правильно проектировать слой модели и не забывать о постоянстве (это может значительно повысить производительность вашего приложения). вы можете использовать <code>core data</code> для этого. но вы <em> не должны </em> забывать, что <code>core data</code> - это не orm или база данных, а диспетчер графов объектов, хорошим вариантом которого является постоянство. таким образом, очень часто <code>core data</code> может быть слишком тяжелым для ваших нужд, и вы можете посмотреть на новые решения, такие как <a href="http://realm.io/">realm</a> и <a href="http://developer.couchbase.com/mobile/index.html">couchbase lite</a> , или создать свой собственный легкий слой отображения объектов/персистентности на основе необработанного sqlite или <a href="https://github.com/google/leveldb">leveldb</a> . также я советую вам ознакомиться с <a href="http://en.wikipedia.org/wiki/domain-driven\_design">domain driven design</a> и <a href="http://martinfowler.com/bliki/cqrs.html">cqrs</a> .</p><p>во-первых, я думаю, что мы <em> должны </em> создать еще один уровень для работы в сети, потому что нам не нужны жирные контроллеры или тяжелые, перегруженные модели. я не верю в эти <code>fat model, skinny controller</code> вещи. но я <strong> верю </strong> в подход <code>skinny everything</code>, потому что ни один класс никогда не должен быть толстым. все сети могут быть в целом абстрагированы как бизнес-логика, следовательно, у нас должен быть другой уровень, где мы можем его разместить. <a href="http://martinfowler.com/eaacatalog/servicelayer.html">сервисный уровень</a> это то, что нам нужно:</p><pre><code>it encapsulates the application's business logic, controlling transactions and coordinating responses in the implementation of its operations. </code></pre><p>в нашем <code>mvc</code> царстве <code>service layer</code> является чем-то вроде посредника между моделью домена и контроллерами. существует довольно похожий вариант этого подхода, называемый <a href="https://softwareengineering.stackexchange.com/questions/184396/mvcs-model-view-controller-store">mvcs</a> , где <code>store</code> фактически является нашим слоем <code>service</code>. <code>store</code> передает экземпляры модели и управляет сетью, кэшированием и т. д. я хочу упомянуть, что вы <em> не должны </em> записывать всю свою сетевую и бизнес-логику на уровне сервисов. это также можно считать плохим дизайном. для получения дополнительной информации посмотрите на доменные модели <a href="http://www.martinfowler.com/bliki/anemicdomainmodel.html">anemic</a> и <a href="http://www.substanceofcode.com/2007/01/17/from-anemic-to-rich-domain-model/">rich</a> . некоторые методы обслуживания и бизнес-логика могут быть обработаны в модели, так что это будет модель "с богатым поведением".</p><p>я всегда широко использую две библиотеки: <a href="https://github.com/afnetworking/afnetworking">afnetworking 2.</a> и <a href="https://github.com/reactivecocoa/reactivecocoa">reactivecocoa</a> . я думаю, что это <em> должно иметь </em> для любого современного приложения, которое взаимодействует с сетью и веб-сервисами или содержит сложную логику пользовательского интерфейса.</p><p><strong>архитектура</strong><br></p><p>сначала я создаю общий класс <code>apiclient</code>, который является подклассом <a href="http://cocoadocs.org/docsets/afnetworking/2.0.0/classes/afhttpsessionmanager.html">afhttpsessionmanager</a> . это рабочая лошадка всей сети в приложении: все классы обслуживания делегируют ему фактические rest запросы. он содержит все настройки http-клиента, которые мне нужны в конкретном приложении: ssl-закрепление, обработка ошибок и создание простых объектов <code>nserror</code> с подробными причинами сбоев и описаниями всех <code>api</code> и ошибок подключения (в таком случае контроллер сможет отображать правильные сообщения для пользователя), настройка сериализаторов запросов и ответов, http-заголовков и прочего, связанного с сетью. затем я логически делю все запросы api на подуслуги или, точнее, <a href="http://www.infoq.com/articles/microservices-intro">microservices</a> : <code>userserivces</code>, <code>commonservices</code>, <code>securityservices</code>, <code>friendsservices</code> и так далее, в соответствии с бизнес-логикой, которую они реализуют. каждый из этих микросервисов является отдельным классом. они вместе образуют <code>service layer</code>. эти классы содержат методы для каждого запроса api, обрабатывают модели домена и всегда возвращают <code>racsignal</code> с проанализированной моделью ответа или <code>nserror</code> вызывающей стороне.</p><p>я хочу упомянуть, что если у вас сложная логика сериализации модели - тогда создайте для нее еще один слой: что-то вроде <a href="http://martinfowler.com/eaacatalog/datamapper.html">data mapper</a> , но более общий, например. json/xml -> картограф модели. если у вас есть кеш: создайте его как отдельный слой/сервис (не следует смешивать бизнес-логику с кешированием). зачем? потому что правильный кеширующий слой может быть довольно сложным с собственными ошибками. люди реализуют сложную логику, чтобы получить правильное, предсказуемое кэширование, например, моноидальное кеширование с проекциями на основе профункторов. вы можете прочитать об этой прекрасной библиотеке под названием <a href="https://github.com/weltn24/carlos">carlos</a> , чтобы понять больше. и не забывайте, что core data может действительно помочь вам со всеми проблемами кэширования и позволит вам писать меньше логики. кроме того, если у вас есть некоторая логика между <code>nsmanagedobjectcontext</code> и моделями запросов к серверу, вы можете использовать шаблон <a href="http://martinfowler.com/eaacatalog/repository.html">repository</a> , который отделяет логику, которая извлекает данные, и сопоставляет ее с моделью сущностей от бизнес-логики, которая действует на модель. поэтому я советую использовать шаблон репозитория, даже если у вас есть архитектура на основе core data. хранилище может абстрагировать такие вещи, как <code>nsfetchrequest</code>, <code>nsentitydescription</code>, <code>nspredicate</code> и так далее, в простые методы, такие как <code>get</code> или <code>put</code>.</p><p>после всех этих действий на уровне service вызывающая сторона (контроллер представления) может выполнить некоторые сложные асинхронные действия с ответом: манипулирование сигналом, сцепление, отображение и т.д. с помощью примитивов <code>reactivecocoa</code>, или просто подписаться на него и показать результаты в посмотреть. я вставляю с помощью <a href="http://martinfowler.com/articles/injection.html">dependency injection</a> во все эти классы обслуживания мой <code>apiclient</code>, который преобразует конкретный вызов службы в соответствующий запрос <code>get</code>, <code>post</code>, <code>put</code>, <code>delete</code> и т.д. к rest конечная точка. в этом случае <code>apiclient</code> неявно передается всем контроллерам, вы можете сделать это явным образом с помощью параметризованных классов обслуживания <code>apiclient</code>. это может иметь смысл, если вы хотите использовать различные настройки <code>apiclient</code> для определенных классов обслуживания, но если по каким-то причинам вам не нужны дополнительные копии или вы уверены, что всегда будете использовать один конкретный экземпляр (без настроек) <code>apiclient</code> - сделать его одиночным, но не делайте, пожалуйста, не делайте классы обслуживания как одиночные.</p><p>затем каждый контроллер представления снова с помощью di вводит необходимый ему класс обслуживания, вызывает соответствующие методы обслуживания и объединяет их результаты с логикой пользовательского интерфейса. для внедрения зависимостей мне нравится использовать <a href="https://github.com/railsware/bloodmagic">bloodmagic</a> или более мощный фреймворк <a href="http://typhoonframework.org/">typhoon</a> . я никогда не использую одиночные игры, класс бога <code>apimanagerwhatever</code> или другие неправильные вещи. потому что если вы называете свой класс <code>whatevermanager</code>, это указывает на то, что вы не знаете его назначение, и это <a href="http://blog.codinghorror.com/i-shall-call-it-somethingmanager/">неправильный выбор дизайна</a> . singletons также является анти-паттерном, и в <strong> большинстве </strong> (кроме редких) случаев <em> неправильно </em> решение. синглтон следует рассматривать только в том случае, если выполнены все три из следующих критериев:</p><ol> <li>право собственности на один экземпляр не может быть разумно присвоено;</li> <li>ленивая инициализация желательна;</li> <li>глобальный доступ не предусмотрен иным образом.</li> </ol><p>в нашем случае владение отдельным экземпляром не является проблемой, и нам также не нужен глобальный доступ после того, как мы разделили нашего менеджера богов на сервисы, потому что теперь только одному или нескольким выделенным контроллерам нужна определенная служба (например, <code>userprofile</code> контроллер нуждается в <code>userservices</code> и т.д. на).</p><p>мы всегда должны уважать принцип <code>s</code> в <a href="http://en.wikipedia.org/wiki/solid\_%28object-oriented\_design%29">solid</a> и использовать <a href="http://weblogs.asp.net/arturtrosin/separation-of-concern-vs-single-responsibility-principle-soc-vs-srp">разделение интересов</a> , поэтому не помещайте все свои методы обслуживания и вызовы сети в один класс, потому что это безумие, особенно если вы разрабатываете крупное корпоративное приложение. вот почему мы должны учитывать внедрение зависимостей и сервисный подход. я считаю этот подход современным и <a href="http://www.warski.org/blog/2015/02/in-todays-post-oo-world-is-dependency-injection-still-relevant/">post-oo</a> . в этом случае мы разделяем наше приложение на две части: управляющую логику (контроллеры и события) и параметры.</p><blockquote> <p>один вид параметров - это обычные параметры данных. это то, что мы передаем функциям, манипулируем, модифицируем, сохраняем и т.д. это сущности, агрегаты, коллекции, классы дел. другой вид - это "служебные" параметры. это классы, которые инкапсулируют бизнес-логику, позволяют общаться с внешними системами, обеспечивают доступ к данным.</p> </blockquote><p>вот общий рабочий процесс моей архитектуры на примере. предположим, у нас есть <code>friendsviewcontroller</code>, который отображает список друзей пользователя, и мы можем удалить его из друзей. я создаю метод в своем классе <code>friendsservices</code> под названием:</p><pre><code>- (racsignal *)removefriend:(friend * const)friend </code></pre><p>где <code>friend</code> - это объект модели/домена (или это может быть просто объект <code>user</code>, если они имеют похожие атрибуты). в рамках этого метода этот метод анализирует <code>friend</code> и <code>nsdictionary</code> параметров json <code>friend\_id</code>, <code>name</code>, <code>surname</code>, <code>friend\_request\_id</code> и так далее. я всегда использую библиотеку <a href="https://github.com/mantle/mantle">mantle</a> для такого рода шаблонов и для моего модельного уровня (разбор вперед и назад, управление иерархиями вложенных объектов в json и т.д.). после анализа он вызывает метод <code>apiclient</code> <code>delete</code>, чтобы выполнить фактический запрос rest, и возвращает <code>response</code> в <code>racsignal</code> вызывающей стороне (<code>friendsviewcontroller</code> в нашем случае), чтобы отобразить соответствующее сообщение для пользователя или любого другого объекта.</p><p>если наше приложение очень большое, мы должны отделить нашу логику еще яснее. например. не <em> не всегда </em> хорошо смешивать <code>repository</code> или модель с логикой <code>service</code>. когда я описывал свой подход, я сказал, что метод <code>removefriend</code> должен находиться в слое <code>service</code>, но если мы будем более педантичны, мы можем заметить, что он лучше принадлежит <code>repository</code>. давайте вспомним, что такое репозиторий. эрик эванс дал точное описание в своей книге [ddd]:</p><blockquote> <p>репозиторий представляет все объекты определенного типа как концептуальный набор. он действует как коллекция, за исключением более сложных запросов.</p> </blockquote><p>таким образом, <code>repository</code> по сути является фасадом, который использует семантику стиля collection (add, update, remove) для предоставления доступа к данным/объектам. вот почему, когда у вас есть что-то вроде: <code>getfriendslist</code>, <code>getusergroups</code>, <code>removefriend</code>, вы можете поместить его в <code>repository</code>, потому что семантика, подобная коллекции, здесь довольно ясна. и код как:</p><pre><code>- (racsignal *)approvefriendrequest:(friendrequest * const)request; </code></pre><p>это определенно бизнес-логика, поскольку она выходит за рамки базовых операций <code>crud</code> и соединяет два объекта домена (<code>friend</code> и <code>request</code>), поэтому ее следует поместить в слой <code>service</code>. также хочу заметить: <strong> не создавайте ненужных абстракций </strong>. используйте все эти подходы с умом. потому что если вы переполните свое приложение абстракциями, это <em> увеличит </em> его случайную сложность и сложность <a href="http://shaffner.us/cs/papers/tarpit.pdf">вызывает больше проблем</a> в программных системах, чем что-либо еще</p><p>я описываю вам "старый" пример objective-c, но этот подход может быть очень легко адаптирован для языка swift с гораздо большим количеством улучшений, потому что он имеет более полезные функции и функциональный сахар. я настоятельно рекомендую использовать эту библиотеку: <a href="https://github.com/ashfurrow/moya">моя</a> . это позволяет вам создать более элегантный слой <code>apiclient</code> (наша рабочая лошадка, как вы помните). теперь наш провайдер <code>apiclient</code> будет типом значения (enum) с расширениями, соответствующими протоколам и использующими сопоставление с образцом деструктуризации. swift enums + сопоставление с образцом позволяет нам создавать <a href="https://en.wikipedia.org/wiki/algebraic\_data\_type">алгебраические типы данных</a> как в классическом функциональном программировании. наши микросервисы будут использовать этого улучшенного поставщика <code>apiclient</code>, как в обычном подходе objective-c. для слоя модели вместо <code>mantle</code> вы можете использовать <a href="https://github.com/hearst-dd/objectmapper">библиотека objectmapper</a> или мне нравится использовать более элегантную и функциональную <a href="https://github.com/thoughtbot/argo">argo</a> библиотека.</p><p>итак, я описал свой общий архитектурный подход, который, я думаю, можно адаптировать для любого приложения. конечно, может быть намного больше улучшений. я советую вам изучить функциональное программирование, потому что вы можете извлечь из этого много пользы, но и не заходите слишком далеко. устранение избыточного общего разделяемого глобального изменчивого состояния, создание <a href="http://blog.zilverline.com/2011/02/01/towards-an-immutable-domain-model-introduction-part-1/">модель неизменяемого домена</a> или создание чистых функций без внешних побочных эффектов, как правило, является хорошей практикой, и новый язык <code>swift</code> поощряет это. но всегда помните, что перегружая ваш код тяжелыми чисто функциональными шаблонами, теоретико-категоричные подходы - это <em> плохая </em> идея, потому что <em> другие </em> разработчики будет читать и поддерживать ваш код, и они могут быть расстроены или напуганы <code>prismatic profunctors</code> и подобными вещами в вашей неизменной модели. то же самое с <code>reactivecocoa</code>: не <code>racify</code> ваш код <a href="https://github.com/reactivecocoa/reactivecocoa#when-to-use-reactivecocoa">слишком много</a> , потому что он может стать нечитаемым очень быстро, особенно для новичков. используйте его, когда это действительно может упростить ваши цели и логику.</p><p>итак, <code>read a lot, mix, experiment, and try to pick up the best from different architectural approaches</code>. это лучший совет, который я могу вам дать.</p>

<p>в соответствии с целью этого вопроса, я хотел бы описать наш архитектурный подход.</p><h2>архитектурный подход</h2><p>архитектура нашего общего приложения для ios основана на следующих шаблонах: <a href="https://en.wikipedia.org/wiki/service\_layers\_pattern">сервисные уровни</a> , <a href="https://en.wikipedia.org/wiki/model%e2%80%93view%e2%80%93viewmodel">mvvm</a> , <a href="https://en.wikipedia.org/wiki/ui\_data\_binding">привязка данных пользовательского интерфейса</a> , <a href="https://en.wikipedia.org/wiki/dependency\_injection">внедрение зависимостей</a> знак равно и <a href="https://en.wikipedia.org/wiki/functional\_reactive\_programming">функционально-реактивное программирование</a> парадигма.</p><p>мы можем разделить типичное приложение, обращенное к потребителю, на следующие логические уровни:</p><ul> <li>сборочный</li> <li>модель</li> <li>сервисы</li> <li>место хранения</li> <li>менеджеры</li> <li>координаторы</li> <li>ui</li> <li>инфраструктура</li> </ul><p><strong> сборочный уровень </strong> является точкой bootstrap нашего приложения. он содержит контейнер внедрения зависимостей и объявления объектов приложения и их зависимостей. этот слой также может содержать конфигурацию приложения (url-адреса, сторонние сервисные ключи и т.д.). для этого мы используем библиотеку <a href="https://github.com/appsquickly/typhoon">typhoon</a> .</p><p><strong> слой модели </strong> содержит классы моделей домена, проверки, сопоставления. мы используем библиотеку <a href="https://github.com/mantle/mantle">mantle</a> для отображения наших моделей: она поддерживает сериализацию/десериализацию в формат <code>json</code> и модели <code>nsmanagedobject</code>. для проверки и представления формы наших моделей мы используем библиотеки <a href="https://github.com/nicklockwood/fxforms">fxforms</a> и <a href="https://github.com/plandem/fxmodelvalidation">fxmodelvalidation</a> .</p><p><strong> сервисный уровень </strong> объявляет сервисы, которые мы используем для взаимодействия с внешними системами с целью отправки или получения данных, представленных в нашей модели предметной области. поэтому обычно у нас есть службы для связи с серверными api-интерфейсами (для каждой сущности), службами обмена сообщениями (например, <a href="https://www.pubnub.com/">pubnub</a> ), службами хранения (например, amazon s3) и т.д. в основном службы обертывают объекты, предоставляемые sdk (например, pubnub). sdk) или реализовать собственную коммуникационную логику. для общих сетей мы используем <a href="https://github.com/afnetworking/afnetworking">afnetworking</a> библиотека.</p><p><strong> уровень хранилища </strong> предназначен для организации локального хранения данных на устройстве. для этого мы используем core data или <a href="https://realm.io/">realm</a> (оба имеют свои плюсы и минусы, решение о том, что использовать, основано на конкретных спецификациях). для настройки core data мы используем <a href="https://github.com/mmorey/mdmcoredata">mdmcoredata</a> библиотеку и набор классов - хранилищ <a href="https://github.com/reactivecocoa/reactivecocoa">аналогично сервисам), которые предоставляют доступ к локальному хранилищу для каждого объекта. для realm мы просто используем подобные хранилища, чтобы иметь доступ к локальному хранилищу.</p><p><strong> уровень менеджеров </strong> - это место, где живут наши абстракции/оболочки.</p><p>в роли менеджера может быть:</p><ul> <li>диспетчер учетных данных с различными реализациями (связка ключей, nsdefaults, ...)</li> <li>менеджер текущего сеанса, который знает, как сохранить и обеспечить текущий сеанс пользователя</li> <li>capture pipeline, обеспечивающий доступ к мультимедийным устройствам (запись видео, аудио, фотосъемка)</li> <li>ble manager, обеспечивающий доступ к службам и периферийным устройствам bluetooth</li> <li>geo location manager</li> <li>...</li> </ul><p>таким образом, в роли менеджера может выступать любой объект, реализующий логику определенного аспекта или задачи, необходимую для работы приложения.</p><p>мы стараемся избегать синглетонов, но этот слой - место, где они живут, если они необходимы.</p><p><strong> уровень координаторов </strong> предоставляет объекты, которые зависят от объектов других уровней (service, storage, model), чтобы объединить их логику в одну последовательность работы, необходимую для определенных модуль (функция, экран, пользовательская история или пользовательский опыт). обычно он объединяет асинхронные операции и знает, как реагировать на их успехи и неудачи. в качестве примера вы можете представить функцию обмена сообщениями и соответствующий объект <code>messagingcoordinator</code>. обработка операции отправки сообщения может выглядеть так:</p><ol> <li>проверить сообщение (слой модели)</li> <li>сохранить сообщение локально (хранилище сообщений)</li> <li>загрузить вложение сообщения (сервис amazon s3)</li> <li>обновите статус сообщения и url-адреса вложений и сохраните сообщение локально (хранилище сообщений)</li> <li>сериализация сообщения в формате json (слой модели)</li> <li>опубликовать сообщение в pubnub (сервис pubnub)</li> <li>обновите статус и атрибуты сообщений и сохраните их локально (хранилище сообщений)</li> </ol><p>на каждом из вышеперечисленных шагов ошибка обрабатывается соответственно.</p><p><strong> уровень пользовательского интерфейса </strong> состоит из следующих подслоев:</p><ol> <li>viewmodels</li> <li>viewcontrollers</li> <li>просмотры</li> </ol><p>чтобы избежать контроллеров massive view, мы используем шаблон mvvm и реализуем логику, необходимую для представления пользовательского интерфейса во viewmodels. viewmodel обычно имеет координаторов и менеджеров в качестве зависимостей. viewmodels, используемые viewcontrollers и некоторыми видами представлений (например, ячейки табличного представления). склеивание между viewcontrollers и viewmodels - это привязка данных и шаблон команд. для того, чтобы получить этот клей, мы используем библиотеку --- (reactivecocoa</a> .</p><p>мы также используем reactivecocoa и его концепцию <code>racsignal</code> в качестве интерфейса и возвращаемого типа значения всех методов координаторов, служб, хранилищ. это позволяет нам связывать операции, запускать их параллельно или последовательно, а также многие другие полезные вещи, предоставляемые reactivecocoa.</p><p>мы пытаемся реализовать наше поведение пользовательского интерфейса декларативным способом. привязка данных и автоматическое расположение очень помогают в достижении этой цели.</p><p><strong> уровень инфраструктуры </strong> содержит все помощники, расширения, утилиты, необходимые для работы приложения.</p><hr><p>этот подход хорошо работает для нас и тех типов приложений, которые мы обычно создаем. но вы должны понимать, что это всего лишь субъективный подход, который <strong> должен </strong> быть адаптирован/изменен для целей конкретной команды.</p><p>надеюсь, что это поможет вам!</p><p>также вы можете найти больше информации о процессе разработки ios в этом посте <a href="http://www.sm-cloud.com/ios-development-as-a-service/">ios development as a service</a> </p>

<p>поскольку все приложения для ios разные, я думаю, что здесь есть разные подходы, но я обычно иду по этому пути:<br> создайте класс центрального менеджера (singleton) для обработки всех запросов api (обычно называемых apicommunicator), и каждый метод экземпляра является вызовом api. и есть один центральный (не публичный) метод:</p><blockquote> <p>-<code>(racsignal *)sendgettoservertosubpath:(nsstring *)path withparameters:(nsdictionary *)params;</code></p> </blockquote><p>для записи я использую 2 основных библиотеки/фреймворка, reactivecocoa и afnetworking. reactivecocoa отлично справляется с асинхронными сетевыми ответами, что вы можете сделать (sendnext :, senderror: и т.д.).<br> этот метод вызывает api, получает результаты и отправляет их через rac в "сыром" формате (например, nsarray, который возвращает afnetworking).<br> затем такой метод, как <code>getstufflist:</code>, который вызвал вышеуказанный метод, подписывается на его сигнал, анализирует необработанные данные в объекты (с чем-то вроде motis) и отправляет объекты один за другим вызывающему (<code>getstufflist:</code>), и подобные методы также возвращают сигнал, что контроллер может подписаться).<br> подписанный контроллер получает объекты с помощью блока <code>subscribenext:</code> и обрабатывает их.<br> <br> я пробовал много способов в разных приложениях, но этот работал лучше всего из всех, поэтому я недавно использовал его в нескольких приложениях, он подходит как для небольших, так и для больших проектов, и его легко расширять и поддерживать, если что-то должен быть изменен.<br> надеюсь, это поможет, я хотел бы услышать мнение других о моем подходе и, возможно, как другие думают, что это может быть улучшено.</p>

<p>в моей ситуации я обычно использую библиотеку <a href="https://github.com/restkit/restkit">reskit</a> для настройки сетевого уровня. это обеспечивает простой в использовании анализ. это уменьшает мои усилия по настройке сопоставления для разных ответов и прочего.</p><p>я только добавляю некоторый код, чтобы настроить отображение автоматически. я определяю базовый класс для своих моделей (не протокол из-за большого количества кода, чтобы проверить, реализован ли какой-либо метод или нет, и меньше кода в самих моделях):</p><p><b>mappableentry.h</b></p><pre><code>@interface mappableentity : nsobject + (nsarray*)pathpatterns; + (nsarray*)keypathes; + (nsarray*)fieldsarrayformapping; + (nsdictionary*)fieldsdictionaryformapping; + (nsarray*)relationships; @end </code></pre><p><b>mappableentry.m</b></p><pre><code>@implementation mappableentity +(nsarray*)pathpatterns { return @[]; } +(nsarray*)keypathes { return nil; } +(nsarray*)fieldsarrayformapping { return @[]; } +(nsdictionary*)fieldsdictionaryformapping { return @{}; } +(nsarray*)relationships { return @[]; } @end </code></pre><p>отношения - это объекты, которые представляют вложенные объекты в ответ:</p><p><b>relationshipobject.h</b></p><pre><code>@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 </code></pre><p><b>relationshipobject.m</b></p><pre><code>@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 </code></pre><p>затем я настраиваю отображение для restkit следующим образом:</p><p><b>objectmappinginitializer.h</b></p><pre><code>@interface objectmappinginitializer : nsobject +(void)initializerkobjectmanagermapping:(rkobjectmanager*)objectmanager; @end </code></pre><p><b>objectmappinginitializer.m</b></p><pre><code>@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 </code></pre><p>некоторые примеры реализации mappableentry:</p><p><b>user.h</b></p><pre><code>@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 </code></pre><p><b>user.m</b></p><pre><code>@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 </code></pre><hr><p>теперь о упаковке запросов:</p><p>у меня есть заголовочный файл с определением блоков, чтобы уменьшить длину строки во всех классах apirequest:</p><p><b>apicallbacks.h</b></p><pre><code>typedef void(^successcallback)(); typedef void(^successcallbackwithobjects)(nsarray *objects); typedef void(^errorcallback)(nserror *error); typedef void(^progressblock)(float progress); </code></pre><p>и пример моего класса apirequest, который я использую:</p><p><b>loginapi.h</b></p><pre><code>@interface loginapi : nsobject - (void)loginwithcredentials:(credentials*)credentials onsuccess:(successcallbackwithobjects)onsuccess onerror:(errorcallback)onerror; @end </code></pre><p><b>loginapi.m</b></p><pre><code>@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 </code></pre><p>и все, что вам нужно сделать в коде, просто инициализировать объект api и вызывать его всякий раз, когда вам это нужно:</p><p><b>someviewcontroller.m</b></p><pre><code>@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 </code></pre><p>мой код не идеален, но его легко установить один раз и использовать для разных проектов. если это кому-нибудь интересно, я бы мог потратить некоторое время и сделать для него универсальное решение где-нибудь на github и cocoapods.</p>

<p>на мой взгляд, вся архитектура программного обеспечения определяется необходимостью. если это для обучения или личных целей, тогда определите основную цель и задайте ей архитектуру. если это работа по найму, то бизнес-необходимость имеет первостепенное значение. хитрость заключается в том, чтобы не позволить блестящим вещам отвлечь вас от реальных потребностей. мне трудно это сделать. в этом бизнесе всегда появляются новые блестящие вещи, и многие из них бесполезны, но вы не всегда можете сказать это заранее. сосредоточьтесь на необходимости и будьте готовы отказаться от неправильного выбора, если можете.</p><p>например, недавно я сделал быстрый прототип приложения для обмена фотографиями для местного бизнеса. поскольку бизнес-необходимость заключалась в том, чтобы сделать что-то быстрое и грязное, архитектура в конечном итоге представляла собой некоторый код ios для вызова камеры и некоторый сетевой код, присоединенный к кнопке отправки, которая загружала изображение в хранилище s3 и записывала его в домен simpledb. код был тривиальным, а стоимость минимальной, и у клиента есть масштабируемая коллекция фотографий, доступная через интернет с помощью rest звонков. дешевое и глупое, приложение имело много недостатков и иногда блокировало пользовательский интерфейс, но было бы бесполезно делать больше для прототипа, и это позволяло бы им развертывать свои кадры и легко генерировать тысячи тестовых изображений без производительности или масштабируемости. проблемы. дрянная архитектура, но она подходит по необходимости и стоит отлично.</p><p>другой проект включал реализацию локальной защищенной базы данных, которая синхронизируется с системой компании в фоновом режиме, когда сеть доступна. я создал фоновый синхронизатор, который использовал restkit, так как в нем было все, что мне нужно. но мне пришлось написать так много пользовательского кода для restkit, чтобы иметь дело с уникальным json, что я мог бы сделать все это быстрее, написав свой собственный преобразование json в coredata. однако заказчик захотел принести это приложение на себя, и я чувствовал, что restkit будет похож на фреймворки, которые они использовали на других платформах. я жду, чтобы увидеть, было ли это хорошее решение.</p><p>опять же, проблема для меня заключается в том, чтобы сосредоточиться на необходимости и позволить этому определять архитектуру. я стараюсь изо всех сил избегать использования сторонних пакетов, поскольку они приносят затраты, которые появляются только после того, как приложение находится в поле некоторое время. я стараюсь избегать создания иерархий классов, поскольку они редко окупаются. если я могу написать что-то в разумные сроки вместо того, чтобы принять пакет, который не подходит идеально, тогда я сделаю это. мой код хорошо структурирован для отладки и соответствующим образом прокомментирован, но сторонние пакеты редко встречаются. с учетом сказанного, я считаю, что af networking слишком полезен, чтобы его игнорировать, хорошо структурировать, хорошо комментировать и поддерживать, и я его часто использую! restkit покрывает много общих случаев, но я чувствую, что я боролся, когда я его использую, и большинство источников данных, с которыми я сталкиваюсь, полны причуд и проблем, которые лучше всего решать с помощью пользовательского кода. в моих последних нескольких приложениях я просто использую встроенные конвертеры json и пишу несколько служебных методов.</p><p>один шаблон, который я всегда использую, состоит в том, чтобы получать сетевые вызовы от основного потока. последние 4-5 приложений, которые я сделал, настроили фоновую задачу таймера, используя dispatch\_source\_create, который периодически просыпается и выполняет сетевые задачи по мере необходимости. вы должны выполнить некоторую работу по обеспечению безопасности потоков и убедиться, что код, изменяющий пользовательский интерфейс, отправляется в основной поток. это также помогает выполнить вход/инициализацию таким образом, чтобы пользователь не чувствовал себя обремененным или задержанным. пока это работает довольно хорошо. я предлагаю изучить эти вещи.</p><p>наконец, я думаю, что по мере того, как мы будем больше работать и развивать ос, мы будем стремиться к разработке более эффективных решений. мне потребовались годы, чтобы преодолеть мою веру в то, что я должен следовать шаблонам и конструкциям, которые, по мнению других людей, являются обязательными. если я работаю в контексте, где это является частью местной религии, хм, я имею в виду лучшие инженерные практики департамента, тогда я следую обычаям, которые они мне платят. но я редко нахожу, что следование старым проектам и моделям является оптимальным решением. я всегда стараюсь смотреть на решение сквозь призму потребностей бизнеса и строить архитектуру, которая бы соответствовала ему и делала все как можно проще. когда я чувствую, что там недостаточно, но все работает правильно, тогда я на правильном пути.</p>

<p>я использую подход, который я получил здесь: <a href="https://github.com/constantine-fry/foursquare-api-v2">https://github.com/constantine-fry/foursquare-api-v2</a> . я переписал эту библиотеку в swift, и вы можете увидеть архитектурный подход из этих частей кода:</p><pre><code>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}} } </code></pre><p>по существу, существует подкласс nsoperation, который создает nsurlrequest, анализирует ответ json и добавляет блок обратного вызова с результатом в очередь. основной класс api создает nsurlrequest, инициализирует этот подкласс nsoperation и добавляет его в очередь.</p>

<p>мы используем несколько подходов в зависимости от ситуации. для большинства вещей afnetworking - это самый простой и надежный подход в том, что вы можете устанавливать заголовки, загружать многокомпонентные данные, использовать get, post, put & delete и есть множество дополнительных категорий для uikit, которые позволяют вам, например, установить изображение из url в сложном приложении с большим количеством вызовов мы иногда абстрагируем это до собственного удобного метода, который будет выглядеть примерно так:</p><pre><code>-(void)makerequesttourl:(nsurl *)url withparameters:(nsdictionary *)parameters success:(void (^)(id responseobject))success failure:(void (^)(afhttprequestoperation *operation, nserror *error))failure; </code></pre><p>есть несколько ситуаций, когда afnetworking не подходит, например, когда вы создаете каркас или другой компонент библиотеки, поскольку afnetworking уже может быть в другой кодовой базе. в этой ситуации вы будете использовать nsmutableurlrequest либо встроенным, если вы делаете один вызов, либо абстрагируете в класс запроса/ответа.</p>

<p>я избегаю одиночек при разработке своих приложений. они типичны для многих людей, но я думаю, что вы можете найти более элегантные решения в других местах. обычно я создаю свои сущности в coredata, а затем помещаю свой код rest в категорию nsmanagedobject. например, если бы я хотел создать и post нового пользователя, я бы сделал это:</p><pre><code>user* newuser = [user createinmanagedobjectcontext:managedobjectcontext]; [newuser postonsuccess:^(...) { ... } onfailure:^(...) { ... }]; </code></pre><p>я использую restkit для сопоставления объектов и инициализирую его при запуске. я считаю, что маршрутизация всех ваших звонков через единый канал - пустая трата времени и добавляет много ненужных шаблонов.</p><p>в nsmanagedobject + extensions.m:</p><pre><code>+ (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]; } </code></pre><p>в nsmanagedobject + networking.m:</p><pre><code>- (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]; } </code></pre><p>зачем добавлять дополнительные вспомогательные классы, если вы можете расширить функциональность общего базового класса с помощью категорий?</p><p>если вы заинтересованы в более подробной информации о моем решении, дайте мне знать. я рад поделиться.</p>

<p>на этот вопрос уже есть много отличных и обширных ответов, но я чувствую, что должен упомянуть об этом, потому что никто больше не имеет.</p><p>alamofire для swift. <a href="https://github.com/alamofire/alamofire">https://github.com/alamofire/alamofire</a> </p><p>он создан теми же людьми, что и afnetworking, но более прямо разработан с учетом swift.</p>

<p>с чисто классовой точки зрения у вас обычно будет что-то вроде этого:</p><ul> <li>ваши <strong> контроллеры представлений </strong> управляют одним или несколькими представлениями</li> <li> <p><strong> класс модели данных </strong> - это действительно зависит от того, сколько реальных сущностей вы имеете дело, и как они связаны.</p> <p>например, если у вас есть массив элементов для отображения в четырех различных представлениях (список, диаграмма, график и т.д.), у вас будет один класс модели данных для списка элементов, и еще один для элемента. список <em> класса элементов </em> будет использоваться четырьмя контроллерами представления - всеми потомками контроллера панели вкладок или контроллера навигации.</p> <p>классы модели данных пригодятся не только для отображения данных, но и для их сериализации, при этом каждый из них может представлять свой собственный формат сериализации с помощью методов экспорта json/xml/csv (или чего-либо еще).</p> </li> <li> <p>важно понимать, что вам также нужны <strong> классы построителя запросов api </strong>, которые сопоставляются напрямую с вашими конечными точками api rest. допустим, у вас есть api, который регистрирует пользователя - поэтому ваш класс конструктора api входа создаст post полезную нагрузку json для api входа в систему. в другом примере класс построителя запросов api для списка элементов каталога api создаст строку запроса get для соответствующего api и сгенерирует запрос rest get.</p> <p>эти классы построителя запросов api обычно получают данные от контроллеров представления, а также передают те же данные обратно в контроллеры представления для обновления пользовательского интерфейса/других операций. затем контроллеры представлений решат, как обновить объекты модели данных этими данными.</p> </li> <li> <p>наконец, <a href="http://www.iphonegamezone.net/ios-app-design-blog-how-to-get-json-data-from-rest-api/">сердце клиента rest</a> - <a href="https://github.com/vividcode/iosapidataapp/tree/master/iosapidataapp"><strong> класс средства извлечения данных api </strong></a> = который игнорирует все виды запросов api, которые делает ваше приложение. этот класс, скорее всего, будет синглтоном, но, как отмечали другие, он не обязательно должен быть синглтоном.</p> <p>обратите внимание, что ссылка является просто типичной реализацией и не принимает во внимание сценарии, такие как сеанс, файлы cookie и т.д., но этого достаточно, чтобы начать работу без использования сторонних сред.</p> </li> </ul>

<p>попробуйте <a href="https://github.com/kevin0571/stnettaskqueue">https://github.com/kevin0571/stnettaskqueue</a> </p><p>создавайте запросы api в отдельных классах.</p><p>stnettaskqueue будет работать с потоками и делегировать/обратный вызов.</p><p>выдвижной для разных протоколов.</p>