Web services en Objective-C con RestKit

http://www.ingens-networks.com/blog/post/2012/05/09/Web-services-en-Objective-C-con-RestKit.aspx

Obtener la respuesta de un Web Service en Cocoa/Objective-C no es muy complicado. De hecho, podemos encontrar muchísimas librerías que nos puede ayudar con este cometido como wsdl2objc , JSONKit , csoap , sudzc… pero hay uno en especial que se diferencia del resto “RestKit”.

RestKit, a simple vista, puede parecer otra librería más para consumir servicios web. Sin embargo, su enfoque es radicalmente diferente, ya que no sólo sirve para consumir dichos servicios, sino también mapeo de objetos (deserializar el JSON a objetos en Objective-C) y puede persistir estos objetos a nivel local a través de Core Data lo que nos ahorrará muchas líneas de código y horas.

 

Este post lo iba a dividir en dos, uno para la instalación en Xcode 4.x y otro sería la introducción a la librería pero creo que para la instalación os dejaré el siguiente enlace donde lo explica de una manera muy fácil y visual.

 

Instalación de RestKit en Xcode 4.x como un submódulo de Git.

 


USANDO RestKit


Para este tutorial, vamos a consumir un web service con la siguiente estructura básica:

1
[{"VersionID":1,"Description":"Una descripción cualquiera"}]

 

En nuestro AppDelegate.h incluiremos la librería de RestKit e implementaremos los protocolos RKRequestDelegate y RKObjectLoaderDelegate.

1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
#import <RestKit/RestKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RKRequestDelegate, RKObjectLoaderDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

Modificamos en AppDelegate.m el método application:didFinishLaunchingWithOptions: para configurar nuestro cliente RKClient (realizaremos la mayoría de peticiones al servidor a través de esta clase).

1
2
3
4
5
6
7
8
9
10
11
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Configuramos nuestro cliente RKClient para que haga una petición GET a http://www.ingens-networks.com/services/version. El cual nos devolverá el JSON deseado
    RKClient *client = [RKClient clientWithBaseURLString:@"http://www.ingens-networks.com"];
    [client get:@"/services/version" delegate:self];
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Ahora deberemos implementar el método delegado request:didLoadResponse: del protocolo RKRequestDelegate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma mark RKRequestDelegate methods
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {  
   if ([request isGET]) { 
        // Comprobamos que lo devuelto es JSON
        if ([response isJSON]) { 
            NSLog(@"Contenido JSON: %@", [response bodyAsString]); 
        }
    }
    else if ([response isNotFound])
    {      // No ha devuelto nada 
            NSLog(@"El recurso con ruta '%@' no ha sido encontrado.", [request resourcePath]); 
    }
}

Si todo ha salido bien, se nos ha tenido que pintar por consola el contenido del JSON. Ahora incluiremos dos métodos delegados del protocolo RKObjectLoaderDelegate y modificaremos algo application:didFinishLaunchingWithOptions: para que RestKit nos haga el mapeo de objetos (el proceso mediante el cual vamos a convertir el contenido del JSON en un objeto de Objective-C).

 

MAPEO DE OBJETOS


Como vimos antes, el contenido del JSON se compone de dos campos “VersionID” y “Description”. Por lo que crearemos una clase Version que tendrá dos propiedades (versionID -> NSNumber / descripcion -> NSString).

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
@interface Version : NSObject
@property (nonatomic, retain) NSNumber *versionID;
@property (nonatomic, retain) NSString *descripcion;
@end

Para llevar a cabo una operación de asignación de objetos, RestKit requiere que se configure una instancia de RKObjectMapping para instruir al asignador sobre cómo manejar sus datos. De una manera más sencilla, RKObjectMapping sirve como plantilla en el que vamos a indicar “este campo de JSON se corresponde con este otro atributo en mi clase…”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Obtener JSON Version
    RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[Version class]];
    [objectMapping mapKeyPath:@"VersionID" toAttribute:@"versionID"];
    [objectMapping mapKeyPath:@"Descripcion" toAttribute:@"descripcion"];
    RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURLString:@"http://www.ingens-networks.com"];
    //[manager loadObjectsAtResourcePath:@"/services/version" objectMapping:objectMapping delegate:self];
    [manager.mappingProvider setMapping:objectMapping forKeyPath:@""];
    [manager loadObjectsAtResourcePath:@"/services/version" delegate:self];
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

La línea 8 está comentada porque era la manera en la que se llamaba al método loadObjectsAtResource: pero está “deprecated”, así que la manera de solucionarlo está escrita en las líneas 9 y 10.

 

Para terminar, deberemos implementar dos métodos delegados del protocolo RKObjectLoaderDelegate (objectLoader:didLoadObjects: y objectLoader:didFailWithError:). Uno de ellos será para trabajar con los datos obtenidos del JSON y el otro para comprobar si ha existido algún error en el proceso.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma mark RKObjectLoaderDelegate methods
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
    for (int i=0; i<objects.count; i++) {
        // Comprobamos si los datos es del tipo Version
        if ([[objects objectAtIndex:i] isKindOfClass:[Version class]]) {
            Version *version = [objects objectAtIndex:i];
            NSLog(@"Cargado Version ID # %@ -> Descripcion: %@", version.versionID, version.descripcion);
        }
        else{
            NSLog(@"No es de la clase Version");
        
    }
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
    NSLog(@"Se ha encontrado un error: %@", error);
}