Los mejores enfoques arquitectónicos para crear aplicaciones de red iOS (clientes REST)


Soy un desarrollador de iOS con algo de experiencia y esta pregunta es realmente interesante para mí. Vi una gran cantidad de diferentes recursos y materiales sobre este tema, pero sin embargo todavía estoy confundido. ¿Cuál es la mejor arquitectura para una aplicación en red iOS? Me refiero a un marco abstracto básico, patrones, que se ajustará a todas las aplicaciones de red, ya sea una aplicación pequeña que solo tiene unas pocas solicitudes de servidor o un cliente REST complejo. Apple recomienda usar MVC como arquitectura básica enfoque para todas las aplicaciones iOS, pero ni MVC ni los patrones más modernos MVVM explican dónde poner el código lógico de red y cómo organizarlo en general.

¿Necesito desarrollar algo como MVCS(S para Service) y en esta capa Service poner todas las solicitudes API y otra lógica de red, que en perspectiva puede ser realmente compleja? Después de hacer algunas investigaciones, encontré dos enfoques básicos para esto. Aquí se recomendó crear una clase separada para cada solicitud de red para el servicio web de API (como LoginRequest clase o PostCommentRequest clase y así sucesivamente) que hereda de la base de la solicitud resumen de la clase AbstractBaseRequest y además de crear algunas global network manager que encapsula común código de red y otras preferencias (puede ser AFNetworking personalización o RestKit tuning, si la tenemos complejo de asignaciones de objeto y la persistencia, o incluso una red propia de comunicación de la aplicación con la API estándar). Pero este enfoque me parece una sobrecarga. Otro el enfoque es tener algún singleton API despachador o clase manager como en el primer enfoque, pero no para crear clases para cada solicitud y en su lugar encapsular cada solicitud como un método público de instancia de esta clase manager como: fetchContacts, loginUser métodos, etc. Entonces, ¿cuál es la mejor y correcta manera? ¿Hay otros enfoques interesantes que aún no conozco?

Y debería crear otra capa para todas estas cosas de redes como Service, o NetworkProvider capa o lo que sea en la parte superior de mi arquitectura MVC, o esta capa debe integrarse (inyectarse) en capas MVC existentes, por ejemplo, Model?

Sé que existen enfoques hermosos, o cómo, entonces, monstruos móviles como el cliente de Facebook o el cliente de LinkedIn lidian con la creciente complejidad exponencial de la lógica de redes?

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

Author: Community, 2014-06-11

11 answers

I want to understand basic, abstract and correct architectural approach for networking applications in iOS : no existe ningún "el mejor", o "el más correcto" enfoque para construir una arquitectura de aplicación. Es un trabajo muy creativo. Siempre debes elegir la arquitectura más sencilla y extensible, que quedará clara para cualquier desarrollador, que empiece a trabajar en tu proyecto o para otros desarrolladores de tu equipo, pero estoy de acuerdo, que puede haber una arquitectura "buena" y una "mala".

Usted dijo: collect the most interesting approaches from experienced iOS developers, no creo que mi enfoque sea el más interesante o correcto, pero lo he usado en varios proyectos y satisfecho con él. Es un enfoque híbrido de los que ha mencionado anteriormente, y también con mejoras de mis propios esfuerzos de investigación. Me interesan los problemas de los enfoques de construcción, que combinan varios patrones y modismos conocidos. Creo que muchos de patrones empresariales de Fowler se pueden aplicar con éxito a las aplicaciones móviles. Aquí hay una lista de los más interesantes, que podemos solicitar crear una arquitectura de aplicación iOS ( en mi opinión): Capa de Servicio, Unidad De Trabajo, Fachada Remota, Objeto de Transferencia de Datos, Puerta de enlace, Supertipo de Capa, Caso Especial, Domain Model . Siempre debe diseñar correctamente una capa de modelo y siempre no se olvide de la persistencia (puede aumentar significativamente el rendimiento de su aplicación). Puedes usar Core Data para esto. Pero tú deberías no olvida, que Core Data no es un OR o una base de datos, sino un gestor de grafos de objetos con persistencia como una buena opción de ello. Por lo tanto, muy a menudo Core Data puede ser demasiado pesado para sus necesidades y puede buscar nuevas soluciones como Realm y Couchbase Lite, o construir su propia capa ligera de mapeo de objetos/persistencia, basada en raw SQLite o LevelDB. También le aconsejo que se familiarice con el Domain Driven Design y CQRS .

Al principio, creo que deberíamos crear otra capa para redes, porque no queremos controladores gordos o modelos pesados y abrumados. No creo en esas fat model, skinny controller cosas. Pero yo creo en skinny everything enfoque, porque ninguna clase debe ser grasa, nunca. Todas las redes se pueden abstraer generalmente como lógica de negocio, por lo tanto debemos tener otra capa, donde podemos ponerlo. La capa de servicio es lo que necesidad:

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

En nuestro MVC reino Service Layer es algo así como un mediador entre el modelo de dominio y los controladores. Hay una variación bastante similar de este enfoque llamado MVCS donde un Store es en realidad nuestra capa Service. Store vende instancias de modelo y maneja la red, el almacenamiento en caché, etc. Quiero mencionar que no debería escribir toda su lógica de red y negocio en su capa de servicio. Esto también puede ser considerado como un mal diseño. Para más info mira los modelos de dominio Anémico y Rico. Algunos métodos de servicio y lógica de negocio se pueden manejar en el modelo, por lo que será un modelo "rico" (con comportamiento).

Siempre uso extensivamente dos bibliotecas: AFNetworking 2.0 y ReactiveCocoa. Creo que es un debe tener para cualquier aplicación moderna que interactúe con la red y los servicios web o que contenga lógica de interfaz de usuario compleja.

ARQUITECTURA

Al principio creo una clase general APIClient, que es una subclase de AFHTTPSessionManager. Este es un caballo de batalla de todas las redes en la aplicación: todas las clases de servicio delegan solicitudes REST reales a ella. Contiene todas las personalizaciones del cliente HTTP, que necesito en la aplicación en particular: fijación SSL, procesamiento de errores y creación de objetos NSError sencillos con razones de falla detalladas y descripciones de todos API y errores de conexión (en tal caso el controlador podrá mostrar mensajes correctos para el usuario), configurando serializadores de solicitud y respuesta, encabezados http y otras cosas relacionadas con la red. Luego divido lógicamente todas las solicitudes de API en subservicios o, más correctamente, microservicios: UserSerivces, CommonServices, SecurityServices, FriendsServices y así sucesivamente, de acuerdo con la lógica de negocio que implementan. Cada uno de estos microservicios es una clase separada. Ellos, juntos, forman a Service Layer. Estas clases contienen métodos para cada solicitud de API, procesa modelos de dominio y siempre devuelve un RACSignal con el modelo de respuesta analizado o NSError al llamador.

Quiero mencionar que si tiene una lógica de serialización de modelo compleja, entonces cree otra capa para ella: algo como Data Mapper pero más general, por ejemplo, JSON/XML - > Model mapper. Si tiene caché: entonces créelo como una capa/servicio independiente también (no debe mezclar la lógica de negocios con el almacenamiento en caché). ¿Por qué? Debido a que la capa de almacenamiento en caché correcta puede ser bastante complejo con sus propias trampas. Las personas implementan lógica compleja para obtener un almacenamiento en caché válido y predecible como, por ejemplo, el almacenamiento en caché 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 almacenamiento en caché y le permitirá escribir menos lógica. Además, si tiene alguna lógica entre NSManagedObjectContext y los modelos de solicitudes del servidor, puede usar el patrón Repository , que separa la lógica que recupera los datos y los asigna al modelo de entidad de la lógica de negocio que actúa sobre el modelo. Por lo tanto, aconsejo usar el patrón de repositorio incluso cuando tiene una arquitectura basada en datos básicos. El repositorio puede abstraer cosas, como NSFetchRequest,NSEntityDescription, NSPredicate y así sucesivamente a métodos simples como get o put.

Después de todas estas acciones en la capa de servicio, el llamante (controlador de vista) puede hacer algunas cosas complejas asíncronas con la respuesta: manipulaciones de señal, encadenamiento, mapeo, etc. con la ayuda de ReactiveCocoa primitivas , o simplemente suscribirse a él y mostrar los resultados en la vista. Inyecto la Inyección de dependencia en todas estas clases de servicio mi APIClient, que traducirá una llamada de servicio en particular a la correspondiente GET, POST, PUT, DELETE, etc. solicitud al punto final REST. En este caso, APIClient se pasa implícitamente a todos los controladores, puede hacer esto explícito con una clase de servicio parametrizada sobre APIClient. Esto puede tener sentido si quieres para usar diferentes personalizaciones de APIClient para clases de servicio particulares, pero si ,por alguna razón, no desea copias adicionales o está seguro de que siempre usará una instancia particular (sin personalizaciones) de APIClient - hágalo un singleton, pero NO LO HAGA, por favor NO haga clases de servicio como singleton.

Luego, cada controlador de vista de nuevo con el DI inyecta la clase de servicio que necesita, llama a los métodos de servicio apropiados y compone sus resultados con la lógica de la interfaz de usuario. Para inyección de dependencia Me gusta usar BloodMagic o un framework más poderoso Typhoon . Nunca uso singletons, clase de Dios APIManagerWhatever u otras cosas equivocadas. Porque si llamas a tu clase WhateverManager, esto indica que no conoces su propósito y es una mala elección de diseño. Singletons es también un anti-patrón, y en la mayoría de casos (excepto los raros) es una solución incorrecta. Singleton debe ser considerado solo si los tres de los siguientes criterios están satisfechos:

  1. No se puede asignar razonablemente la propiedad de la instancia única;{[189]]}
  2. La inicializació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 dividir nuestro administrador de dios en servicios, porque ahora solo uno o varios controladores dedicados necesitan un servicio en particular (por ejemplo, UserProfile el controlador necesita UserServices y en).

Siempre debemos respetar el principio S en SOLID y usar separation of concerns, así que no pongas todos tus métodos de servicio y llamadas a redes en una clase, porque es una locura, especialmente si desarrollas una gran aplicación empresarial. Es por eso que debemos considerar la inyección de dependencia y el enfoque de servicios. Considero que este enfoque es moderno y post-OO. En este caso dividimos nuestra aplicación en dos partes: lógica de control (controladores y eventos) y parámetros.

Un tipo de parámetros serían los parámetros ordinarios de "datos". Eso es lo que pasamos funciones, manipulamos, modificamos, persistimos, etc. Estas son entidades, agregados, colecciones, clases de casos. El otro tipo serían los parámetros de "servicio". Estas son clases que encapsulan la lógica de negocio, permiten la comunicación con sistemas externos, proporcionan acceso a datos.

Aquí hay un flujo de trabajo general de mi arquitectura por ejemplo. Vamos supongamos que tenemos un FriendsViewController, que muestra la lista de amigos del usuario y tenemos una opción para eliminar de amigos. Creo un método en mi clase FriendsServices llamado:

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

Donde Friend es un objeto modelo/dominio (o puede ser solo un objeto User si tienen atributos similares). Underhood este método analiza Friend a NSDictionary de los parámetros JSONfriend_id, name, surname, friend_request_id y así sucesivamente. Siempre uso la biblioteca Mantle para este tipo de repeticiones y para mi capa de modelo (parsing back y adelante, administrar jerarquías de objetos anidados en JSON, etc.). Después de analizar llamadas APIClient DELETE método para hacer una petición REST real y devuelve Response en RACSignal al llamador (FriendsViewController en nuestro caso) para mostrar el mensaje 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 claramente. Por ejemplo, no es siempre bueno mezclar Repository o lógica de modelo con Service uno. Cuando describí mi enfoque, dije que removeFriend el método debe estar en la capa Service, pero si vamos a ser más pedantes podemos notar que pertenece mejor a Repository. Recordemos qué es el Repositorio. Eric Evans le dio una descripción precisa en su libro [DDD]:

Un repositorio representa todos los objetos de un determinado tipo como un conjunto conceptual. Actúa como una colección, excepto con una capacidad de consulta más elaborada.

Entonces, a Repository es esencialmente una fachada que usa semántica de estilo de colección (Add, Update, Eliminar) para proporcionar acceso a datos / objetos. Es por eso que cuando tienes algo como: getFriendsList, getUserGroups, removeFriend puedes colocarlo en 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 negocio, porque está más allá de las operaciones básicas CRUD y conecta dos objetos de dominio (Friend y Request), por eso debe colocarse en la capa Service. También quiero notar: no cree abstracciones innecesarias. Usar todo estos enfoques sabiamente. Porque si va a abrumar su aplicación con abstracciones, esto aumentará su complejidad accidental, y la complejidad causa más problemas en los sistemas de software que cualquier otra cosa

Les describo un ejemplo "antiguo" de Objective-C, pero este enfoque puede ser muy fácil de adaptar para el lenguaje Swift con muchas más mejoras, porque tiene características más útiles y sugar funcional. Recomiendo encarecidamente utilizar esta biblioteca: Moya . Le permite crear una capa APIClient más elegante (nuestro caballo de batalla como usted recuerda). Ahora nuestro proveedor APIClient será un tipo de valor (enum) con extensiones que se ajustan a los protocolos y aprovechan la coincidencia de patrones de desestructuración. Swift enums + pattern matching nos permite crear tipos de datos algebraicos como en la programación funcional clásica. Nuestros microservicios utilizarán este proveedor APIClient mejorado como en el enfoque habitual de Objective-C. Para la capa de modelo en lugar de Mantle puede use la biblioteca ObjectMapper o me gusta usar la biblioteca Argo más elegante y funcional.

Entonces, describí mi enfoque arquitectónico general, que puede ser adaptado para cualquier aplicación, creo. Puede haber muchas más mejoras, por supuesto. Te aconsejo que aprendas programación funcional, porque puedes beneficiarte mucho de ella, pero no vayas demasiado lejos con ella también. Eliminar el estado mutable excesivo, compartido y global, creando un modelo de dominio inmutable o crear funciones puras sin efectos secundarios externos es, generalmente, una buena práctica, y el nuevo lenguaje Swift fomenta esto. Pero siempre recuerde, que sobrecargar su código con patrones funcionales puros pesados, enfoques teóricos de categoría es una idea mala , porque otros desarrolladores leerán y apoyarán su código, y pueden sentirse frustrados o asustados de prismatic profunctors y ese tipo de cosas en su modelo inmutable. Lo mismo con el ReactiveCocoa: no RACify su código demasiado, porque puede llegar a ser ilegible muy rápido, especialmente para los novatos. Úsalo cuando realmente pueda simplificar tus objetivos y lógica.

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

 308
Author: Oleksandr Karaberov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-12 07:31:22

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

Enfoque de arquitectura

La arquitectura general de nuestra aplicación iOS se basa en los siguientes patrones: Capas de servicio, MVVM, Enlace de Datos de Interfaz de Usuario, Inyección de dependencia ; y paradigma Programación Reactiva Funcional.

Podemos cortar una aplicación típica de cara al consumidor en la siguiente lógica capas:

  • Asamblea
  • Modelo
  • Servicios
  • Almacenamiento
  • Gerentes
  • Coordinadores
  • UI
  • Infraestructura

Assembly layer es un punto de arranque de nuestra aplicación. Contiene un contenedor de Inyección de dependencias y declaraciones de los objetos de la aplicación y sus dependencias. Esta capa también puede contener la configuración de la aplicación (url, claves de servicios de terceros, etc.). Para este propósito usamos la biblioteca Typhoon .

Model layer contiene clases de modelos de dominio, validaciones, asignaciones. Usamos la biblioteca Mantle para mapear nuestros modelos: admite serialización/deserialización en formato JSON y modelos NSManagedObject. Para la validación y representación de formularios de nuestros modelos utilizamos las bibliotecas FXForms y FXModelValidation.

Services layer declara los servicios que utilizamos para interactuar con sistemas externos en para enviar o recibir datos que están representados en nuestro modelo de dominio. Por lo general, tenemos servicios para la comunicación con API de servidor (por entidad), servicios de mensajería (como PubNub), servicios de almacenamiento (como Amazon S3), etc. Básicamente, los servicios empaquetan objetos proporcionados por SDK (por ejemplo, PubNub SDK) o implementan su propia lógica de comunicación. Para redes generales utilizamos la biblioteca AFNetworking .

El propósito de Storage layer es organizar el almacenamiento local de datos en el dispositivo. Utilizamos Core Data oRealm para esto (ambos tienen pros y contras, la decisión de qué usar se basa en especificaciones concretas). Para la configuración de Core Data usamos la biblioteca MDMCoreData y un montón de clases - almacenamientos - (similares a los servicios) que proporcionan acceso al almacenamiento local para cada entidad. Para Realm solo usamos almacenamientos similares para tener acceso al almacenamiento local.

Managers layer es un lugar donde viven nuestras abstracciones/envoltorios.

En un gestor el papel podría ser:

  • Gestor de credenciales con sus diferentes implementaciones (keychain, NSDefaults, ...)
  • Administrador de sesión actual que sabe cómo mantener y proporcionar la sesión de usuario actual
  • Canalización de captura que proporciona acceso a dispositivos multimedia (grabación de video, audio, toma de fotografías)
  • BLE Manager que proporciona acceso a servicios bluetooth y periféricos
  • Geo Location Manager
  • ...

Entonces, en el papel de gerente podría ser cualquier objeto que implementa 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.

Coordinators layer proporciona objetos que dependen de objetos de otras capas (Servicio, Almacenamiento, Modelo) para combinar su lógica en una secuencia de trabajo necesaria para cierto módulo (característica, pantalla, historia de usuario o experiencia de usuario). Por lo general, las cadenas operaciones asíncronas y sabe cómo reaccionar en sus casos de éxito y fracaso. Como ejemplo, puede imaginar una función de mensajería y el objeto MessagingCoordinator correspondiente. El manejo de la operación de envío de mensajes podría tener este aspecto:

  1. Validar mensaje (capa modelo)
  2. Guardar mensaje localmente (almacenamiento de mensajes)
  3. Archivo adjunto de mensaje de carga (servicio amazon s3)
  4. Actualice el estado del mensaje y las url de los archivos adjuntos y guarde el mensaje localmente (almacenamiento de mensajes)
  5. Serializar el mensaje en formato JSON (capa modelo)
  6. Publicar mensaje en PubNub (servicio PubNub)
  7. Actualice el estado y los atributos de los mensajes y guárdelos localmente (almacenamiento de mensajes)

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

La capa UI consiste en las siguientes subcapas:

  1. ViewModels
  2. ViewController
  3. Vistas

Para evitar Controladores de Vista Masivos, usamos MVVM pattern and implement logic needed for UI presentation in ViewModels. Un ViewModel generalmente tiene coordinadores y gerentes como dependencias. ViewModels usados por ViewController y algunos tipos de Vistas (por ejemplo, celdas de vista de tabla). El pegamento entre ViewController y ViewModels es el enlace de datos y el patrón de comandos. Para que sea posible tener ese pegamento utilizamos la biblioteca ReactiveCocoa .

También utilizamos ReactiveCocoa y su concepto RACSignal como interfaz y valor de retorno tipo de todos los coordinadores, servicios, métodos de almacenamiento. Esto nos permite encadenar operaciones, ejecutarlas de forma paralela o en serie, y muchas otras cosas útiles que proporciona ReactiveCocoa.

Tratamos de implementar nuestro comportamiento de interfaz de usuario de manera declarativa. El enlace de datos y el Diseño automático ayudan mucho a lograr este objetivo.

Infrastructure layer contiene todos los helpers, extensiones, utilidades necesarias para el trabajo de la aplicación.


Este enfoque funciona bien para nosotros y para aquellos tipos de aplicaciones que solemos construir. Pero usted debe entender, que esto es solo un enfoque subjetivo que debe ser adaptado/cambiado para el propósito concreto del equipo.

¡Espero que esto te ayude!

También puedes encontrar más información sobre el proceso de desarrollo de iOS en esta entrada de blog Desarrollo de iOS como servicio

 29
Author: Alex Petropavlovsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-15 07:45:11

Debido a que todas las aplicaciones de iOS son diferentes, creo que hay diferentes enfoques aquí para considerar, pero por lo general voy de esta manera:
Cree una clase central manager (singleton) para manejar todas las solicitudes de API (generalmente 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 el registro, utilizo 2 bibliotecas/marcos principales, ReactiveCocoa y AFNetworking. ReactiveCocoa maneja redes asíncronas respuestas perfectamente, 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 formato' 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 objetos (con algo como Motis) y envía los objetos uno por uno a la persona que llama (getStuffList: y métodos similares también devuelven una señal a la que el controlador puede suscribirse).
El el controlador suscrito recibe los objetos por el bloque subscribeNext: y los maneja.

Probé muchas maneras en diferentes aplicaciones, pero esta funcionó mejor de todas, así que he estado usando esto en algunas aplicaciones recientemente, se adapta a proyectos pequeños y grandes y es fácil de extender y mantener si algo necesita ser modificado.
Espero que esto ayude, me gustaría escuchar las opiniones de otros sobre mi enfoque y tal vez cómo otros piensan que esto podría mejorarse.

 17
Author: Rickye,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-11 12:32:06

En mi situación suelo usar la biblioteca ResKit para configurar la capa de red. Proporciona un análisis fácil de usar. Reduce mi esfuerzo en configurar el mapeo para diferentes respuestas y cosas.

Solo añado algún código para configurar la asignación automáticamente. Defino la clase base para mis modelos (no protocolo debido a la gran cantidad de código para comprobar si algún método se implementa o no, y menos código en los propios modelos):

Entrada mapable.h

@interface MappableEntity : NSObject

+ (NSArray*)pathPatterns;
+ (NSArray*)keyPathes;
+ (NSArray*)fieldsArrayForMapping;
+ (NSDictionary*)fieldsDictionaryForMapping;
+ (NSArray*)relationships;

@end

Entrada mapable.m

@implementation MappableEntity

+(NSArray*)pathPatterns {
    return @[];
}

+(NSArray*)keyPathes {
    return nil;
}

+(NSArray*)fieldsArrayForMapping {
    return @[];
}

+(NSDictionary*)fieldsDictionaryForMapping {
    return @{};
}

+(NSArray*)relationships {
    return @[];
}

@end

Las relaciones son objetos que representan objetos anidados 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

Entonces estoy configurando la asignación para RestKit de la siguiente manera:

ObjectMappingInitializer.h

@interface ObjectMappingInitializer : NSObject

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager;

@end

ObjectMappingInitializer.m

@interface ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses;

@end

@implementation ObjectMappingInitializer

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager {

    NSMutableDictionary *mappingObjects = [NSMutableDictionary dictionary];

    // Creating mappings for classes
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *newMapping = [RKObjectMapping mappingForClass:mappableClass];
        [newMapping addAttributeMappingsFromArray:[mappableClass fieldsArrayForMapping]];
        [newMapping addAttributeMappingsFromDictionary:[mappableClass fieldsDictionaryForMapping]];
        [mappingObjects setObject:newMapping forKey:[mappableClass description]];
    }

    // Creating relations for mappings
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *mapping = [mappingObjects objectForKey:[mappableClass description]];
        for (RelationshipObject *relation in [mappableClass relationships]) {
            [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:relation.source toKeyPath:relation.destination withMapping:[mappingObjects objectForKey:[relation.mappingClass description]]]];
        }
    }

    // Creating response descriptors with mappings
    for (Class mappableClass in [self mappableClasses]) {
        for (NSString* pathPattern in [mappableClass pathPatterns]) {
            if ([mappableClass keyPathes]) {
                for (NSString* keyPath in [mappableClass keyPathes]) {
                    [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:keyPath statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
                }
            } else {
                [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
            }
        }
    }

    // Error Mapping
    RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
    [errorMapping addAttributeMappingsFromArray:[Error fieldsArrayForMapping]];
    for (NSString *pathPattern in Error.pathPatterns) {
        [[RKObjectManager sharedManager] addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)]];
    }
}

@end

@implementation ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses {
    return @[
        [FruiosPaginationResults class],
        [FruioItem class],
        [Pagination class],
        [ContactInfo class],
        [Credentials class],
        [User class]
    ];
}

@end

Algunos ejemplos de aplicación de la entrada cartográfica:

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

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

Ahora sobre el empaquetado de solicitudes:

Tengo un archivo de encabezado con definición de bloques, para reducir la longitud de la línea en todas las clases APIRequest:

APICallbacks.h

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

Y Ejemplo de mi clase APIRequest que estoy usando:

LoginAPI.h

@interface LoginAPI : NSObject

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError;

@end

LoginAPI.m

@implementation LoginAPI

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError {
    [[RKObjectManager sharedManager] postObject:nil path:[NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] parameters:[credentials credentialsData] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        onSuccess(mappingResult.array);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        onError(error);
    }];
}

@end

Y todo lo que necesitas para hacerlo en código, simplemente inicialice el objeto API y llámelo cuando lo necesite:

SomeViewController.m

@implementation SomeViewController {
    LoginAPI *_loginAPI;
    // ...
}

- (void)viewDidLoad {
    [super viewDidLoad];

    _loginAPI = [[LoginAPI alloc] init];
    // ...
}

// ...

- (IBAction)signIn:(id)sender {
    [_loginAPI loginWithCredentials:_credentials onSuccess:^(NSArray *objects) {
        // Success Block
    } onError:^(NSError *error) {
        // Error Block
    }];
}

// ...

@end

Mi código no es perfecto, pero es fácil de configurar una vez y usar para diferentes proyectos. Si es interesante para alguien, mb podría pasar algún tiempo y hacer una solución universal para él en algún lugar en GitHub y CocoaPods.

 7
Author: Andrew Cherkashyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-23 12:14:20

En mi opinión, toda la arquitectura de software está impulsada por la necesidad. Si esto es para fines de aprendizaje o personales, a continuación, decidir el objetivo principal y tienen que conducir la arquitectura. Si se trata de un trabajo de alquiler, entonces la necesidad del negocio es primordial. El truco es no dejar que las cosas brillantes te distraigan de las necesidades reales. Encuentro esto difícil de hacer. Siempre hay nuevas cosas brillantes que aparecen en este negocio y muchas de ellas no son útiles, pero no siempre se puede decir que por adelantado. Centrarse en la necesidad y ser dispuesto a abandonar las malas decisiones si puedes.

Por ejemplo, recientemente hice un prototipo rápido de una aplicación para compartir fotos para un negocio local. Dado que la necesidad del negocio era hacer algo rápido y sucio, la arquitectura terminó siendo un código de iOS para abrir una cámara y un código de red adjunto a un botón de envío que cargaba la imagen en una tienda S3 y escribía en un dominio SimpleDB. El código era trivial y el costo mínimo y el cliente tiene una colección de fotos escalable accesible a través de la web con llamadas REST. Barata y tonta, la aplicación tenía muchos defectos y bloquearía la interfaz de usuario en ocasiones, pero sería un desperdicio hacer más por un prototipo y les permite implementar a su personal y generar miles de imágenes de prueba fácilmente sin problemas de rendimiento o escalabilidad. Arquitectura de mierda, pero se ajustaba a la necesidad y costaba perfectamente.

Otro proyecto consistió en implementar una base de datos segura local que se sincroniza con el sistema de la empresa en segundo plano cuando la red está disponible. Creé un sincronizador de fondo que usaba RestKit ya que parecía tener todo lo que necesitaba. Pero tuve que escribir tanto código personalizado para RestKit para lidiar con JSON idiosincráticos que podría haberlo hecho todo más rápido escribiendo mi propio JSON en transformaciones CoreData. Sin embargo, el cliente quería traer esta aplicación en casa y sentí que RestKit sería similar a los marcos que usaban en otras plataformas. Esperaba ver si era una buena decisión.

De nuevo, el la cuestión para mí es centrarme en la necesidad y dejar que eso determine la arquitectura. Trato como el infierno de evitar el uso de paquetes de terceros, ya que traen costos que solo aparece después de que la aplicación ha estado en el campo durante un tiempo. Yo trato de evitar hacer jerarquías de clase ya que rara vez se paga. Si puedo escribir algo en un período de tiempo razonable en lugar de adoptar un paquete que no encaja perfectamente, entonces lo hago. Mi código está bien estructurado para la depuración y adecuadamente comentado, pero de terceros los paquetes rara vez lo son. Dicho esto, me parece que AF Networking es demasiado útil para ignorar y bien estructurado, bien comentado y mantenido, ¡y lo uso mucho! RestKit cubre muchos casos comunes, pero siento que he estado en una pelea cuando lo uso, y la mayoría de las fuentes de datos que encuentro están llenas de peculiaridades y problemas que se manejan mejor con código personalizado. En mis últimas aplicaciones solo uso los convertidores JSON incorporados y escribo algunos métodos de utilidad.

Un patrón que siempre uso es para obtener el la red llama al hilo principal. Las últimas 4-5 aplicaciones que he hecho configurar una tarea de temporizador en segundo plano utilizando dispatch_source_create que se despierta de vez en cuando y hace tareas de red según sea necesario. Necesitas hacer algo de trabajo de seguridad de subprocesos y asegurarte de que el código de modificación de la interfaz de usuario se envíe al subproceso principal. También ayuda a hacer su incorporación/inicialización de tal manera que el usuario no se sienta agobiado o retrasado. Hasta ahora esto ha funcionado bastante bien. Sugiero mirar en estos cosa.

Finalmente, creo que a medida que trabajamos más y a medida que el sistema operativo evoluciona, tendemos a desarrollar mejores soluciones. Me ha llevado años superar mi creencia de que tengo que seguir patrones y diseños que otras personas afirman que son obligatorios. Si estoy trabajando en un contexto donde eso es parte de la religión local, ejem, me refiero a las mejores prácticas de ingeniería departamentales, entonces sigo las costumbres al pie de la letra, eso es por lo que me están pagando. Pero rara vez encuentro que siguiendo diseños más antiguos y patrones es la solución óptima. Siempre trato de ver la solución a través del prisma de las necesidades del negocio y construir la arquitectura para que coincida y mantener las cosas tan simples como pueden ser. Cuando siento que no hay suficiente, pero todo funciona correctamente, entonces estoy en el camino correcto.

 6
Author: Fran K.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-20 09:33:19

Utilizo el enfoque que he obtenido de aquí: https://github.com/Constantine-Fry/Foursquare-API-v2 . He reescrito esa biblioteca en Swift y se puede ver el enfoque arquitectónico de estas partes del código:

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

Básicamente, hay una subclase NSOperation que hace la NSURLRequest, analiza la respuesta JSON y agrega el bloque de devolución de llamada con el resultado a la cola. La clase API principal construye NSURLRequest, inicializa esa subclase NSOperation y la agrega a la cola.

 4
Author: bzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-19 18:20:31

Utilizamos algunos enfoques dependiendo de la situación. Para la mayoría de las cosas, AFNetworking es el enfoque más simple y robusto, ya que puede establecer encabezados, cargar datos de varias partes, usar GET, POST, PUT & DELETE y hay un montón de categorías adicionales para UIKit que le permiten, por ejemplo, establecer una imagen desde una url. En una aplicación compleja con muchas llamadas a veces abstraemos esto a un método de conveniencia propio que sería algo como:

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

Hay algunos sin embargo, situaciones en las que AFNetworking no es apropiado, como cuando está creando un marco u otro componente de biblioteca, ya que AFNetworking puede estar ya en otra base de código. En esta situación, usaría una NSMutableURLRequest ya sea en línea si está haciendo una sola llamada o abstraída en una clase request / response.

 3
Author: Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-17 09:43:11

Evito los singletons al diseñar mis aplicaciones. Son una opción típica para mucha gente, pero creo que puedes encontrar soluciones más elegantes en otros lugares. Normalmente lo que hago es construir mis entidades en CoreData y luego poner mi código REST en una categoría NSManagedObject. Si por ejemplo quisiera crear y PUBLICAR un nuevo Usuario, haría esto:

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

Utilizo RestKit para el mapeo de objetos y lo inicializo al iniciar. Me parece enrutar todas sus llamadas a través de un singleton para ser un pérdida de tiempo y añade un montón de repeticiones que no es necesario.

En NSManagedObject+Extensiones.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];
}

En NSManagedObject+Networking.m:

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

¿Por qué agregar clases auxiliares adicionales cuando puede extender la funcionalidad de una clase base común a través de categorías?

Si está interesado en información más detallada sobre mi solución, hágamelo saber. Estoy feliz de compartir.

 1
Author: Sandy Chapman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-19 16:29:08

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

Cree solicitudes de API en clases separadas.

STNetTaskQueue se ocupará de los subprocesos y delegar/callback.

Ampliable para diferentes protocolos.

 0
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-05 03:08:36

Desde una perspectiva puramente de diseño de clases, generalmente tendrá algo como esto:

  • Sus controladores de vista controlando una o más vistas
  • Clase de modelo de datos - Realmente depende de cuántas entidades distintas reales está tratando, y cómo están relacionadas.

    Por ejemplo, si tiene una matriz de elementos que se mostrarán en cuatro representaciones diferentes (lista, gráfico, gráfico, etc.), tendrá una clase de modelo de datos para la lista de artículos, uno más por un artículo. La lista de la clase de elemento será compartida por cuatro controladores de vista, todos hijos de un controlador de barra de pestañas o un controlador de navegación.

    Las clases de modelo de datos serán útiles no solo para mostrar datos, sino también para serializarlos en donde cada uno de ellos puede exponer su propio formato de serialización a través de métodos de exportación JSON / XML / CSV (o cualquier otra cosa).

  • Es importante entender que también necesita API request builder classes ese mapa directamente con sus endpoints de API REST. Supongamos que tiene una API que registra al usuario, por lo que su clase Login API builder creará una carga útil POST JSON para la api de inicio de sesión. En otro ejemplo, una clase API request builder para la API list of catalog items creará la cadena GET query para la api correspondiente y activará el RESTO GET query.

    Estas clases de generador de solicitudes de API generalmente recibirán datos de los controladores de vista y también pasarán los mismos datos a los controladores de vista para la actualización de la interfaz de usuario / otras operaciones. Los controladores de vista decidirán entonces cómo actualizar los objetos del Modelo de datos con esos datos.

  • Finalmente, el corazón del cliente REST - API data fetcher class que es ajeno a todo tipo de solicitudes de API que realiza su aplicación. Esta clase será más probable que sea un singleton, pero como otros señalaron, no tiene que ser un singleton.

    Tenga en cuenta que el enlace es solo una implementación típica y no tiene en cuenta escenarios como sesión, cookies, etc., pero es suficiente para que te pongas en marcha sin usar ningún marco de terceros.

 0
Author: Nirav Bhatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-18 20:06:06

Esta pregunta ya tiene muchas respuestas excelentes y extensas, pero siento que tengo que mencionarla ya que nadie más lo ha hecho.

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

Es creado por las mismas personas que AFNetworking, pero está diseñado más directamente con Swift en mente.

 0
Author: matt.writes.code,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-06 17:01:10