Mejores enfoques arquitectónicos para build aplicaciones de networkinges iOS (clientes REST)

Soy un desarrollador de iOS con algo de experiencia y esta pregunta es realmente interesante para mí. Vi muchos resources y materiales diferentes sobre este tema, pero, sin embargo, todavía estoy confundido. ¿Cuál es la mejor architecture para una aplicación en networking iOS? Me refiero a un marco abstracto básico, patrones, que se adaptará a todas las aplicaciones de networking, ya sea una aplicación pequeña que solo tenga unas pocas requestes de server o un cliente REST complejo. Apple recomienda utilizar MVC como un enfoque arquitectónico básico para todas las aplicaciones de iOS, pero ni MVC ni los patrones más modernos de MVVM explican dónde colocar el código de lógica de networking y cómo organizarlo en general.

¿Necesito desarrollar algo como MVCS ( S para el Service ) y en esta capa de Service poner todas API requestes de API y otra lógica de networking, que en perspectiva puede ser realmente compleja? Después de hacer una investigación encontré dos enfoques básicos para esto. Aquí se recomendó crear una class separada para cada request de networking a la API service web (como la class LoginRequest o la class PostCommentRequest y así sucesivamente) que henetworkinga de la class abstracta de request base AbstractBaseRequest y además de crear un administrador de networking global que encapsula código de networking común y otras preferences (puede ser la personalización de AFNetworking o la sintonización de RestKit , si tenemos asignaciones complejas de objects y persistencia, o incluso una implementación propia de comunicación de networking con API estándar). Pero este enfoque me parece una sobrecarga. Otro enfoque es tener un despachador de API Singleton o una class de administrador como en el primer enfoque, pero no para crear classs para cada request y en su lugar para encapsular cada request como un método público de instancia de esta class de administrador como: fetchContacts , loginUser , etc. Entonces, ¿cuál es la mejor y correcta manera? ¿Hay otros enfoques interesantes que no conozco todavía?

¿Debería crear otra capa para todas estas cosas de networkinges como el Service o la capa NetworkProvider o lo que esté encima de mi architecture MVC o esta capa debería integrarse (inyectarse) en las capas MVC existentes, por ejemplo, Model ?

Sé que existen enfoques bellos, o cómo esos monstruos mobilees como el cliente de Facebook o el cliente de LinkedIn lidian con la creciente complejidad de la lógica de networking?

Sé que no hay una respuesta exacta y formal al problema. El objective de esta pregunta es recostackr los enfoques más interesantes de los desarrolladores experimentados de iOS . El mejor enfoque sugerido será marcado como aceptado y premiado con una recompensa de reputación, otros serán revocados. Es principalmente una pregunta teórica y de investigación. Quiero comprender el enfoque arquitectónico básico, abstracto y correcto para las aplicaciones de networking en iOS. Espero una explicación detallada de los desarrolladores experimentados.

I want to understand basic, abstract and correct architectural approach for networking applications in iOS : no existe un I want to understand basic, abstract and correct architectural approach for networking applications in iOS "el mejor" o "el más correcto" para build una architecture de aplicaciones. Es un trabajo muy creativo. Siempre debe elegir la architecture más sencilla y extensible, que será clara para cualquier desarrollador, que comience a trabajar en su proyecto o para otros desarrolladores en su equipo, pero estoy de acuerdo en que puede haber una "buena" y una "mala "architecture

Dijiste: collect the most interesting approaches from experienced iOS developers , no creo que mi enfoque sea el más interesante o correcto, pero lo he utilizado en varios proyectos y estoy satisfecho con él. Es un enfoque híbrido de los que usted mencionó anteriormente, y también con mejoras de mis propios esfuerzos de investigación. Me interesan los problemas de construcción de enfoques, que combinan varios patrones y expresiones conocidas. Creo que muchos de los patrones empresariales de Fowler se pueden aplicar con éxito a las aplicaciones mobilees. Aquí hay una list de los más interesantes, que podemos aplicar para crear una architecture de aplicaciones iOS ( en mi opinión ): capa de service , unidad de trabajo , fachada remota , object de transferencia de datos , puerta de enlace , supertipo de capa , caso especial , model de dominio . Siempre debe diseñar correctamente una capa de model y no olvidarse de la persistencia (puede boost significativamente el performance de su aplicación). Puede usar Core Data para esto. Pero no debes olvidar que Core Data no es un ORM o una database, sino un gestor de charts de objects con persistencia como una buena opción. Por lo tanto, con frecuencia Core Data puede ser demasiado pesado para sus necesidades y puede search soluciones nuevas como Realm y Couchbase Lite o crear su propia capa de persistencia / mapeo de objects ligeros, basada en SQLite sin formatting o LevelDB . También le aconsejo que se familiarice con el layout dominado por el dominio y CQRS .

Al principio, creo, deberíamos crear otra capa para la creación de networkinges, porque no queremos controlleres de grasa o models pesados ​​y abrumados. No creo en ese fat model, skinny controller cosas fat model, skinny controller . Pero sí creo que skinny everything aproxima, porque ninguna class debe ser gorda, nunca. Todas las networkinges pueden generalmente ser abstraídas como lógica de negocio, por lo tanto, deberíamos tener otra capa, donde podemos ponerla. Capa de service es lo que necesitamos:

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

En nuestra Service Layer reino de MVC es algo así como un mediador entre el model de dominio y los controlleres. Hay una variación bastante similar de este enfoque llamado MVCS donde una Store es en realidad nuestra capa de Service . Store vende instancias de model y maneja la networking, el almacenamiento en caching, etc. Quiero mencionar que no debe escribir toda su lógica de networkinges y negocios en su capa de service. Esto también se puede considerar como un mal layout. Para get más información, consulte los models de dominio Anemic y Rich . Algunos methods de service y lógica de negocio se pueden manejar en el model, por lo que será un model "rico" (con comportamiento).

Siempre utilizo extensamente dos bibliotecas: AFNetworking 2.0 y ReactiveCocoa . Creo que es una necesidad para cualquier aplicación moderna que interactúe con la networking y los web services o que contenga una compleja lógica de interfaz de usuario.

ARQUITECTURA

Al principio creo una class APIClient general, que es una subclass de AFHTTPSessionManager . Este es un caballo de batalla de todas las networkinges en la aplicación: todas las classs de service delegan las requestes reales de REST. Contiene todas las personalizaciones del cliente HTTP, lo que necesito en la aplicación particular: fijación de SSL, procesamiento de errores y creación de objects NSError sencillos con razones de falla detalladas y descripciones de todos los errores de API y connection (en tal caso, el controller podrá mostrar lo correcto posts para el usuario), configurar serializadores de requestes y respuestas, encabezados http y otras cosas relacionadas con la networking. Luego UserSerivces lógicamente todas las requestes de la API en subservices o, más correctamente, en microservices : UserSerivces , CommonServices , SecurityServices , FriendsServices , etc., de acuerdo con la lógica comercial que implementan. Cada uno de estos microservices es una class separada. Ellos, juntos, forman una Service Layer . Estas classs contienen methods para cada request de API, models de dominio de process y siempre devuelve un RACSignal con el model de respuesta analizado o NSError al llamante.

Quiero mencionar que si tiene una lógica de serialization de model compleja, entonces cree otra capa para ella: algo así como Data Mapper pero más general, p. Ej. JSON / XML -> Model mapper. Si tiene caching: luego créelo como una capa / service aparte (no debe mezclar lógica de negocio con almacenamiento en caching). ¿Por qué? Debido a que la capa de caching correcta puede ser bastante compleja con sus propias gotchas. Las personas implementan una lógica compleja para get un almacenamiento en caching válido y pnetworkingecible como, por ejemplo, almacenamiento en memory caching monoidal con proyecciones basadas en profunctors. Puedes leer sobre esta hermosa biblioteca llamada Carlos para entender más. Y no olvide que Core Data realmente puede ayudarlo con todos los problemas de caching y le permitirá escribir less lógica. Además, si tiene alguna lógica entre NSManagedObjectContext y los models de requestes del server, puede utilizar el patrón Repositorio , que separa la lógica que recupera los datos y los asigna al model de la entidad desde la lógica empresarial que actúa sobre el model. Entonces, le aconsejo que use el patrón de Repositorio incluso cuando tenga una architecture basada en Core Data. El repository puede abstraer cosas, como NSFetchRequest , NSFetchRequest , NSEntityDescription , etc. a methods simples como get o put .

Después de todas estas acciones en la capa de Servicio, la persona que llama (controller de vista) puede hacer algo complejo y asíncrono con la respuesta: manipulación de señales, encadenamiento, mapeo, etc. con la ayuda de primitivas ReactiveCocoa , o simplemente suscribirse y mostrar resultados en ver. Inyecto con la dependency injection en todas estas classs de service mi APIClient , lo que traducirá una llamada de service particular a la correspondiente POST GET , POST , PUT , DELETE , etc. al punto final REST. En este caso, APIClient se pasa implícitamente a todos los controlleres, puede hacer esto explícito con una parametrización sobre las classs de service APIClient . Esto puede tener sentido si desea utilizar diferentes personalizaciones de APIClient para classs de service particulares, pero si, por algunas razones, no desea copys adicionales o está seguro de que siempre usará una instancia particular (sin personalizaciones) de el APIClient : APIClient en un singleton, pero NO LO HAGAS, NO hagas las classs de service como simples.

Luego, cada controller de vista nuevamente con el DI inyecta la class de service que necesita, llama a los methods de service apropiados y compone sus resultados con la lógica de la interfaz de usuario. Para la dependency injection, me gusta usar BloodMagic o un marco Typhoon más potente. Nunca uso singletons, Dios APIManagerWhatever , sea APIManagerWhatever class u otra cosa equivocada. Porque si llama a su class WhateverManager , esto indica que no conoce su propósito y es una mala elección de layout . Singletons también es un anti-patrón, y en la mayoría de los casos (excepto los raros) es una solución equivocada . Singleton debe considerarse solo si se cumplen los tres criterios siguientes:

  1. La propiedad de la instancia única no puede asignarse razonablemente;
  2. La iniciación perezosa es deseable;
  3. El acceso global no está previsto de otra manera.

En nuestro caso, la propiedad de la instancia única no es un problema y tampoco necesitamos acceso global después de que dividimos a nuestro administrador de Dios en services, porque ahora solo uno o varios controlleres dedicados necesitan un service en particular (por ejemplo, el controller UserProfile necesita UserServices y en).

Siempre debemos respetar el principio S en SOLID y utilizar la separación de las preocupaciones , así que no ponga todos sus methods de service y llamadas a networkinges en una class, porque es una locura, especialmente si desarrolla una gran aplicación empresarial. Es por eso que deberíamos considerar el enfoque de inyección y services de dependencia. Considero este enfoque como moderno y post-OO . En este caso, dividimos nuestra aplicación en dos partes: lógica de control (controlleres y events) y parameters.

Un tipo de parameters serían los parameters ordinarios de "datos". Eso es lo que pasamos por las funciones, manipular, modificar, persistir, etc. Estas son entidades, agregados, collections, classs de casos. El otro tipo serían los parameters de "service". Estas son classs que encapsulan la lógica de negocio, permiten comunicarse con sistemas externos, brindan acceso a los datos.

Aquí hay un flujo de trabajo general de mi architecture por ejemplo. Supongamos que tenemos un FriendsViewController , que muestra la list de amigos del usuario y tenemos una opción para eliminar de amigos. Creo un método en mi class FriendsServices llamada:

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

donde Friend es un model / object de dominio (o puede ser un object de User si tienen attributes similares). Underhood este método analiza Friend to NSDictionary de parameters JSON friend_id , name , surname , friend_request_id , etc. Siempre utilizo la biblioteca Mantle para este tipo de repetición y para mi capa de model (analizar y retroceder, administrar jerarquías de objects nesteds en JSON, etc.). Después de analizarlo, llama APIClient método APIClient DELETE para realizar una request REST real y devuelve la Response en RACSignal a la persona que llama ( FriendsViewController en nuestro caso) para mostrar el post apropiado para el usuario o lo que sea.

Si nuestra aplicación es muy grande, tenemos que separar nuestra lógica aún más clara. Por ejemplo, no siempre es bueno mezclar el Repository o la lógica del model con el Service uno. Cuando describí mi enfoque, había dicho que el método removeFriend debería estar en la capa de Service , pero si vamos a ser más pedantes podemos notar que pertenece mejor al Repository . Recordemos qué es el Repositorio. Eric Evans le dio una descripción precisa en su libro [DDD]:

Un repository representa todos los objects de un cierto tipo como un set conceptual. Actúa como una colección, excepto con una capacidad de consulta más elaborada.

Por lo tanto, un Repository es esencialmente una fachada que utiliza la semántica del estilo de Colección (Agregar, Actualizar, Quitar) para proporcionar acceso a datos / objects. Es por eso que cuando tienes algo así como: getFriendsList , getUserGroups , removeFriend puedes colocarlo en el Repository , porque la semántica de colección es bastante clara aquí. Y código como:

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

es definitivamente una lógica de negocios, porque está más allá de las CRUD básicas de CRUD y conecta dos objects de dominio ( Friend y Request ), por eso debe colocarse en la capa de Service . También quiero darme count: no crea abstracciones innecesarias . Utilice todos estos enfoques sabiamente. Porque si va a abrumar su aplicación con abstracciones, esto boostá su complejidad accidental, y la complejidad causa más problemas en los sistemas de software que cualquier otra cosa

Te describo un ejemplo "antiguo" de Objective-C, pero este enfoque puede adaptarse muy fácilmente para el lenguaje Swift con muchas más mejoras, ya que tiene características más útiles y azúcar funcional. Recomiendo utilizar esta biblioteca: Moya . Te permite crear una capa APIClient más elegante (nuestro caballo de batalla como lo restrings). Ahora nuestro proveedor APIClient será un tipo de valor (enum) con extensiones que se ajusten a los protocolos y aprovechando la coincidencia de patrones de desestructuración. Las lists rápidas + la coincidencia de patrones nos permite crear types de datos algebraicos como en la functional programming clásica. Nuestros microservices utilizarán este proveedor mejorado de APIClient como en el enfoque habitual de Objective-C. Para la capa del model en lugar de Mantle puede usar la biblioteca ObjectMapper o me gusta usar una biblioteca Argo más elegante y funcional.

Entonces, describí mi enfoque arquitectónico general, que puede adaptarse para cualquier aplicación, creo. Puede haber muchas más mejoras, por supuesto. Le aconsejo que aprenda functional programming, porque puede beneficiarse mucho de ella, pero no vaya demasiado lejos. La eliminación de un estado mutable global, excesivo, compartido, la creación de un model de dominio inmutable o la creación de funciones puras sin efectos secundarios externos es, en general, una buena práctica, y el nuevo lenguaje Swift alienta. Pero recuerde siempre que sobrecargar su código con patrones funcionales pesados ​​y puros, los enfoques teóricos de categoría son una mala idea, porque otros desarrolladores leerán y apoyarán su código, y pueden sentirse frustrados o prismatic profunctors de los prismatic profunctors y ese tipo de cosas en tu model inmutable Lo mismo con ReactiveCocoa : no RACify su código demasiado , porque puede volverse ilegible muy rápido, especialmente para principiantes. Úselo cuando realmente pueda simplificar sus objectives y su lógica.

Por lo tanto, read a lot, mix, experiment, and try to pick up the best from different architectural approaches . Es el mejor consejo que puedo darte.

De acuerdo con el objective de esta pregunta, me gustaría describir nuestro enfoque de architecture.

Acercamiento a la architecture

La architecture de nuestra aplicación general de iOS se basa en los siguientes patrones: Capas de service , MVVM , data binding UI , dependency injection ; y el paradigma de Progtwigción Reactiva Funcional .

Podemos dividir una aplicación típica que enfrenta al consumidor en las siguientes capas lógicas:

  • Asamblea
  • Modelo
  • Servicios
  • Almacenamiento
  • Gerentes
  • Coordinadores
  • Interfaz de usuario
  • Infraestructura

La capa de ensamblaje es un punto de arranque de nuestra aplicación. Contiene un contenedor de dependency injection y declaraciones de los objects de la aplicación y sus dependencies. Esta capa también puede contener la configuration de la aplicación (urls, keys de services de terceros, etc.). Para este propósito utilizamos la biblioteca Typhoon .

La capa del model contiene classs de models de dominio, validaciones y mapeos. Utilizamos la biblioteca Mantle para mapear nuestros models: admite serialization / deserialization en formatting JSON y models NSManagedObject . Para la validation y la representación de formularios de nuestros models, utilizamos las bibliotecas FXForms y FXModelValidation .

El nivel de services declara los services que utilizamos para interactuar con sistemas externos con el fin de enviar o recibir datos que están representados en nuestro model de dominio. Por lo general, tenemos services para la comunicación con las API del server (por entidad), services de postría (como PubNub ), services de almacenamiento (como Amazon S3), etc. Básicamente, los services envuelven objects proporcionados por SDK (por ejemplo PubNub SDK) o implementan su propia comunicación lógica. Para networkinges en general, utilizamos la biblioteca AFNetworking .

La finalidad de la capa de almacenamiento es organizar el almacenamiento local de datos en el dispositivo. Usamos Core Data o Realm para esto (ambos tienen ventajas y desventajas, la decisión de qué usar se basa en especificaciones concretas). Para la configuration de Core Data utilizamos la biblioteca MDMCoreData y un montón de classs (almacenamientos) (similares a los services) que brindan acceso a almacenamiento local para cada entidad. Para Realm, simplemente usamos almacenes similares para tener acceso al almacenamiento local.

La capa de administradores es un lugar donde viven nuestras abstracciones / envoltorios.

En una function de administrador podría ser:

  • Cnetworkingentials Manager con sus diferentes implementaciones (llavero, NSDefaults, …)
  • Current Session Manager que sabe cómo mantener y proporcionar la session actual del usuario
  • Capture Pipeline que proporciona acceso a dispositivos multimedia (grabación de video, audio, tomar fotos)
  • Administrador de BLE que proporciona acceso a services y periféricos de bluetooth
  • Geo Location Manager

Por lo tanto, en el rol de administrador podría ser cualquier object que implemente la lógica de un aspecto particular o preocupación necesaria para el funcionamiento de la aplicación.

Tratamos de evitar Singletons, pero esta capa es un lugar donde viven si son necesarios.

La capa de coordinadores proporciona objects que dependen de objects de otras capas (Servicio, Almacenamiento, Modelo) para combinar su lógica en una secuencia de trabajo necesaria para cierto module (function, pantalla, historia del usuario o experiencia del usuario). Por lo general, las cadenas de operaciones asincrónicas y sabe cómo reactjsr en sus casos de éxito y fracaso. Como ejemplo, puede imaginar una function de postría y el correspondiente object MessagingCoordinator . El event handling la operación de envío de posts podría verse así:

  1. Validar post (capa del model)
  2. Guardar post localmente (almacenamiento de posts)
  3. Cargar adjunto de post (service amazon s3)
  4. Actualice el estado del post y las direcciones URL de los files adjuntos y guarde el post localmente (almacenamiento de posts)
  5. Serializar el post en formatting JSON (capa del model)
  6. Publicar post en PubNub (service PubNub)
  7. Actualice el estado y los attributes de los posts y guárdelos localmente (almacenamiento de posts)

En cada uno de los pasos anteriores, un error se maneja de manera correspondiente.

La capa de interfaz de usuario consta de siguientes subcapas:

  1. ViewModels
  2. ViewControllers
  3. Puntos de vista

Para evitar los controlleres Massive View usamos el patrón MVVM e implementamos la lógica necesaria para la presentación de la interfaz de usuario en ViewModels. Un ViewModel generalmente tiene coordinadores y gerentes como dependencies. ViewModels utilizados por ViewControllers y algunos types de vistas (por ejemplo, celdas de vista de tabla). El pegamento entre ViewControllers y ViewModels es el data binding y el patrón de command. Para poder tener ese pegamento utilizamos la biblioteca ReactiveCocoa .

También utilizamos ReactiveCocoa y su concepto RACSignal como una interfaz y tipo de valor de retorno de todos los coordinadores, services, methods de almacenamiento. Esto nos permite encadenar operaciones, ejecutarlas paralelas o en serie, y muchas otras cosas útiles proporcionadas por ReactiveCocoa.

Intentamos implementar nuestro comportamiento de interfaz de usuario de forma declarativa. El data binding y el layout automático ayudan mucho a lograr este objective.

La capa de infraestructura contiene todos los ayudantes, extensiones, utilidades necesarias para el trabajo de la aplicación.


Este enfoque funciona bien para nosotros y para los types de aplicaciones que generalmente creamos. Pero debe comprender que este es solo un enfoque subjetivo que debe adaptarse / cambiarse para el propósito del equipo concreto.

¡Espero que esto te ayudará!

También puedes encontrar más información sobre el process de desarrollo de iOS en esta publicación de blog iOS Development as a Service

Debido a que todas las aplicaciones de iOS son diferentes, creo que hay diferentes enfoques aquí para tener en count, pero suelo ir de esta manera:
Cree una class de administrador central (singleton) para manejar todas las requestes de API (usualmente llamadas APICommunicator) y cada método de instancia es una llamada de API. Y hay un método central (no público):

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

Para que conste, utilizo 2 bibliotecas / frameworks principales, ReactiveCocoa y AFNetworking. ReactiveCocoa maneja perfectamente las respuestas de networking asíncrona, puede hacer (sendNext :, sendError :, etc.).
Este método llama a la API, obtiene los resultados y los envía a través de RAC en formatting 'raw' (como NSArray lo que devuelve AFNetworking).
Luego un método como getStuffList: que llamó al método anterior se suscribe a su señal, analiza los datos sin procesar en objects (con algo así como Motis) y envía los objects uno por uno al llamador ( getStuffList: y methods similares también devuelven una señal de que el el controller puede suscribirse).
El controller suscrito recibe los objects mediante subscribeNext: lockings y los maneja.

Probé muchas maneras en diferentes aplicaciones, pero esta funcionó mejor, por eso he usado esto recientemente en algunas aplicaciones, se adapta a proyectos pequeños y grandes y es fácil de extender y mantener si hay que modificar algo.
Espero que esto ayude, me gustaría escuchar las opiniones de otros acerca de mi enfoque y tal vez cómo otros piensan que tal vez podría mejorarse.

En mi situación, generalmente utilizo la biblioteca ResKit para configurar la capa de networking. Proporciona análisis fáciles de usar. Reduce mi esfuerzo en configurar el mapeo para diferentes respuestas y cosas por el estilo.

Solo agrego un código para configurar la asignación automáticamente. Defino la class base para mis models (no el protocolo debido a la cantidad de código para verificar si algún método está implementado o no, y less código en los models en sí):

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 

Las relaciones son objects que representan objects nesteds en respuesta:

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 

Luego estoy configurando la asignación para RestKit así:

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 shanetworkingManager] 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], [Cnetworkingentials class], [User class] ]; } @end 

Some example of MappableEntry implementation:

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 

Now about the Requests wrapping:

I have header file with blocks definition, to networkinguce line length in all APIRequest classes:

APICallbacks.h

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

And Example of my APIRequest class that I'm using:

LoginAPI.h

 @interface LoginAPI : NSObject - (void)loginWithCnetworkingentials:(Cnetworkingentials*)cnetworkingentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError; @end 

LoginAPI.m

 @implementation LoginAPI - (void)loginWithCnetworkingentials:(Cnetworkingentials*)cnetworkingentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError { [[RKObjectManager shanetworkingManager] postObject:nil path:[NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] parameters:[cnetworkingentials cnetworkingentialsData] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) { onSuccess(mappingResult.array); } failure:^(RKObjectRequestOperation *operation, NSError *error) { onError(error); }]; } @end 

And all you need to do in code, simply initialize API object and call it whenever you need it:

SomeViewController.m

 @implementation SomeViewController { LoginAPI *_loginAPI; // ... } - (void)viewDidLoad { [super viewDidLoad]; _loginAPI = [[LoginAPI alloc] init]; // ... } // ... - (IBAction)signIn:(id)sender { [_loginAPI loginWithCnetworkingentials:_cnetworkingentials onSuccess:^(NSArray *objects) { // Success Block } onError:^(NSError *error) { // Error Block }]; } // ... @end 

My code isn't perfect, but it's easy to set once and use for different projects. If it's interesting to anyone, mb I could spend some time and make a universal solution for it somewhere on GitHub and CocoaPods.

To my mind all software architecture is driven by need. If this is for learning or personal purposes, then decide the primary goal and have that drive the architecture. If this is a work for hire, then the business need is paramount. The trick is to not let shiny things distract you from the real needs. I find this hard to do. There are always new shiny things appearing in this business and lots of them are not useful, but you can't always tell that up front. Focus on the need and be willing to abandon bad choices if you can.

For example, I recently did a quick prototype of a photo sharing app for a local business. Since the business need was to do something quick and dirty, the architecture ended up being some iOS code to pop up a camera and some network code attached to a Send Button that uploaded the image to a S3 store and wrote to a SimpleDB domain. The code was trivial and the cost minimal and the client has an scalable photo collection accessible over the web with REST calls. Cheap and dumb, the app had lots of flaws and would lock the UI on occasion, but it would be a waste to do more for a prototype and it allows them to deploy to their staff and generate thousands of test images easily without performance or scalability concerns. Crappy architecture, but it fit the need and cost perfectly.

Another project involved implementing a local secure database which synchronizes with the company system in the background when the network is available. I created a background synchronizer that used RestKit as it seemed to have everything I needed. But I had to write so much custom code for RestKit to deal with idiosyncratic JSON that I could have done it all quicker by writing my own JSON to CoreData transformations. However, the customer wanted to bring this app in house and I felt that RestKit would be similar to the frameworks that they used on other platforms. I waiting to see if that was a good decision.

Again, the issue to me is to focus on the need and let that determine the architecture. I try like hell to avoid using third party packages as they bring costs that only appears after the app has been in the field for a while. I try to avoid making class hierarchies as they rarely pay off. If I can write something in a reasonable period of time instead of adopting a package that doesn't fit perfectly, then I do it. My code is well structunetworking for debugging and appropriately commented, but third party packages rarely are. With that said, I find AF Networking too useful to ignore and well structunetworking, well commented, and maintained and I use it a lot! RestKit covers a lot of common cases, but I feel like I've been in a fight when I use it, and most of the data sources I encounter are full of quirks and issues that are best handled with custom code. In my last few apps I just use the built in JSON converters and write a few utility methods.

One pattern I always use is to get the network calls off the main thread. The last 4-5 apps I've done set up a background timer task using dispatch_source_create that wakes up every so often and does network tasks as needed. You need to do some thread safety work and make sure that UI modifying code gets sent to the main thread. It also helps to do your onboarding/initialization in such a way that the user doesn't feel burdened or delayed. So far this has been working rather well. I suggest looking into these things.

Finally, I think that as we work more and as the OS evolves, we tend to develop better solutions. It has taken me years to get over my belief that I have to follow patterns and designs that other people claim are mandatory. If I am working in a context where that is part of the local religion, ahem, I mean the departmental best engineering practices, then I follow the customs to the letter, that's what they are paying me for. But I rarely find that following older designs and patterns is the optimal solution. I always try to look at the solution through the prism of the business needs and build the architecture to match it and keep things as simple as they can be. When I feel like there isn't enough there, but everything works correctly, then I'm on the right track.

I use the approach that I've gotten from here: https://github.com/Constantine-Fry/Foursquare-API-v2 . I've rewritten that library in Swift and you can see the architectural approach from these parts of the 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}} } 

Basically, there is NSOperation subclass that makes the NSURLRequest, parses JSON response and adds the callback block with the result to the queue. The main API class constructs NSURLRequest, initialises that NSOperation subclass and adds it to the queue.

We use a few approaches depending on the situation. For most things AFNetworking is the simplest and most robust approach in that you can set headers, upload multipart data, use GET, POST, PUT & DELETE and there are a bunch of additional categories for UIKit which allow you to for example set an image from a url. In a complex app with a lot of calls we sometimes abstract this down to a convenience method of our own which would be something like:

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

There are a few situations where AFNetworking isn't appropriate however such as where you are creating a framework or other library component as AFNetworking may already be in another code base. In this situation you would use an NSMutableURLRequest either inline if you are making a single call or abstracted into a request / response class.

I avoid singletons when designing my applications. They are a typical go to for a lot of people but I think you can find more elegant solutions elsewhere. Typically what I do is a build out my entities in CoreData and then put my REST code in an NSManagedObject category. If for instance I wanted to create and POST a new User, I'd do this:

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

I use RESTKit for the object mapping and initialize it at start up. I find routing all of your calls through a singleton to be a waste of time and adds a lot of boilerplate that isn't needed.

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

In NSManagedObject+Networking.m:

 - (void)getOnSuccess:(RESTSuccess)onSuccess onFailure:(RESTFailure)onFailure blockInput:(BOOL)blockInput { [[RKObjectManager shanetworkingManager] getObject:self path:nil parameters:nil success:onSuccess failure:onFailure]; [self handleInputBlocking:blockInput]; } 

Why add extra helper classes when you can extend the functionality of a common base class through categories?

If you're interested in more detailed info on my solution let me know. I'm happy to share.

Try https://github.com/kevin0571/STNetTaskQueue

Create API requests in separated classes.

STNetTaskQueue will deal with threading and delegate/callback.

Extendable for different protocols.

From a purely class design perspective, you will usually have something like this:

  • Your view controllers controlling one or more views
  • Data model class – It really depends upon how many real distinct entities you are dealing with, and how they are related.

    For example, if you have an array of items to be displayed in four different representations (list, chart, graph etc), you will have one data model class for list of items, one more for an item. The list of item class will be shanetworking by four view controllers – all children of a tab bar controller or a nav controller.

    Data model classes will come handy in not only displaying data, but also serializing them wherein each of them can expose their own serialization format through JSON / XML / CSV (or anything else) export methods.

  • It is important to understand that you also need API request builder classes that map directly with your REST API endpoints. Let's say you have an API that logs the user in – so your Login API builder class will create POST JSON payload for login api. In another example, an API request builder class for list of catalog items API will create GET query string for corresponding api and fire the REST GET query.

    These API request builder classes will usually receive data from view controllers and also pass the same data back to view controllers for UI update / other operations. View controllers will then decide how to update Data Model objects with that data.

  • Finally, the heart of the REST client – API data fetcher class which is oblivious to all sorts of API requests your app makes. This class will more likely be a singleton, but as others pointed out, it doesn't have to be a singleton.

    Note that the link is just a typical implementation and does not take into consideration scenarios like session, cookies etc, but it is enough to get you going without using any 3rd party frameworks.

This question has a lot of excellent and extensive answers already, but I feel I have to mention it since no one else has.

Alamofire for Swift. https://github.com/Alamofire/Alamofire

It's created by the same people as AFNetworking, but is more directly designed with Swift in mind.