Javi Moreno Apuntes Fichas de Lectura Archivo Sobre mi
Blog Logo

Javi


8 minutos de lectura

En la entrada anterior vimos como hacer una web con su API Rest con muy poco esfuerzo. En esta vamos a ver como conectar con ese API Rest con muy poco esfuerzo también. Eso si, vamos a centrarnos solamente en la clase NSURLConnection; la creación de la aplicación, modificación del modelo y del storyboard lo damos por supuesto.

Por si sirve de referencia: para crear la aplicación de ha utilizado la plantilla de Master-Detail, con opción de Core Data y Storyboards, que llamaremos MegaLists. En el detailviewcontroller se ha cambiado la UILabel por un UITextView para que de más juego y en el modelo Core Data, tanto la entidad como los atributos se llaman igual que en la aplicación Rails.

NSURLConnection

Vamos a empezar las cosas bien. En un fichero de cabecera que llamaremos MegaLists.h vamos a crear un par de macros que podremos llegar a utilizar en muchas partes de nuestra aplicación. Este fichero de cabecera luego lo incluiremos en el fichero MegaLists-Prefix.pch para que se incluya en la compilación de cada clase del proyecto. Las macros son estas:

#define SERVER_URL @"http://localhost:3000/"
#define HTTP_TIMEOUT 5.0

De esta forma, cuando vayamos a subir la aplicación a la App Store solo tendremos que cambiar la url local por la del dominio al que nos vayamos a conectar. Igualmente, si queremos aumentar el tiempo de espera de la conexión, de esta forma lo cambiaremos para todas las conexiones. La llamada al servicio web se hará desde la clase MasterViewController. Necesitaremos un par de propiedades más: un NSURL y un NSData. El primero será la url a la que llamaremos y el segundo donde almacenaremos los datos que nos devuelva el servicio. El objeto NSURL lo alocaremos nada más cargar el viewcontroller:

- (void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    self.navigationItem.leftBarButtonItem = self.editButtonItem;
    
    self.serverURL = [NSURL URLWithString:SERVER_URL];
    
}

El método encargado de hacer la conexión se llamará loadDataFromOurWebService, muy descriptivo para que sepamos donde tocar si algo falla y será invocado cada vez que se vea la vista del MasterViewController:

#pragma mark - Conexión al servidor:

- (void)viewWillAppear:(BOOL)animated
{
    [self loadDataFromOurWebService];
}

- (void)loadDataFromOurWebService
{
    NSURL *queryURL =  [self.serverURL URLByAppendingPathComponent:@"lists.json" isDirectory:YES];

    NSURLRequest *request = [NSURLRequest requestWithURL:queryURL
                                             cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                         timeoutInterval:HTTP_TIMEOUT];

    //ASINCRONOOOOOOOO
    NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
    if (connection) {
        NSLog(@"Connecting...");
        // Create the NSMutableData to hold the received data.
        self.receivedData = [[NSMutableData alloc] init];
    } else {
        // Inform the user that the connection failed.
        NSLog(@"ERROR: Unable to create connection.");
    }
}

El metodo connectionWithRequest es asíncrono lo que va a permitir que nuestra aplicación siga siendo usable mientras hacemos la descarga del servidor. Para conocer el éxito o fracaso de nuestra conexión necesitaremos los siguientes métodos delegados que posee la clase NSURLConnection... si no sabemos cuales son, la documentación de Apple nos lo deja bien claro: Using NSURLConnection

#pragma mark - URL Connection delegate

- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"Error: %@", [error localizedDescription]);
}

La verdad es que no es necesario contar que hace el método didFailWithError pero por si acaso, diremos que es el que nos informará, en caso de que haya algún problema, del error que se ha producido en la conexión.

- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    NSLog(@"Received response: %@", response);
    // It can be called multiple times, for example in the case of a redirect, so each time we reset the data.
    [self.receivedData setLength:0];
}

En situaciones normales, solo deberíamos ver una sola vez el método didReceiveResponse, eso quiere decir que el servidor ha aceptado nuestra conexión y va a enviar datos. Si se envía más de un mensaje a este método querrá decir que algo ha ido mal en la conexión y los datos recibidos hasta el momento ya no son válidos. Es por ello que en este método se vuelve a inicializar el NSData.

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.receivedData appendData:data];
}

El método didReceiveData se encargará de ir "pegando" los paquetes de datos que vaya recibiendo. Una vez que se hayan recibido todos los datos, se enviará un mensaje al método connectionDidFinishLoading. En este método, mediante la clase de reciente aparición en iOS 5 NSJSONSerialization, parsearemos los datos recibidos y los enviaremos al método que se encarga de la grabación en la base de datos.

- (void) connectionDidFinishLoading:(NSURLConnection *) connection {

    // Do the JSON parsing.

    NSError *error;
    NSArray *data = [NSJSONSerialization JSONObjectWithData:self.receivedData
                                                    options:kNilOptions
                                                      error:&error];
    if (error) {
        NSString *receivedString = [[NSString alloc] initWithData:self.receivedData
                                                         encoding:NSUTF8StringEncoding];
        NSLog(@"ERROR: Failed to parse JSON response.");
        NSLog(@"Received data: %@", receivedString);
        NSLog(@"Failed to parse JSON: %@", [error localizedDescription]);
    } else {
        NSLog(@"Finished loading: %@", data);
        [self reloadManagedObjectContextWithData:data];
    }

    self.receivedData = nil;
    //    self.inProgress = NO;
}

- (void) reloadManagedObjectContextWithData:(NSArray *)data {
    NSManagedObject *managedObject = nil;
    for (NSDictionary *dictionary in data) {
        managedObject = [NSEntityDescription insertNewObjectForEntityForName:@"Lists" inManagedObjectContext:__managedObjectContext];
        if (!([dictionary objectForKey:@"id"] == [NSNull null])) {
            [managedObject setValue:[dictionary objectForKey:@"id"] forKey:@"id"];
        }
        if (!([dictionary objectForKey:@"name"] == [NSNull null])) {
            [managedObject setValue:[dictionary objectForKey:@"name"] forKey:@"name"];
        }
        if (!([dictionary objectForKey:@"notes"] == [NSNull null])) {
            [managedObject setValue:[dictionary objectForKey:@"notes"] forKey:@"notes"];
        }
    }
    NSError *error = nil;
    if (![__managedObjectContext save:&error]) {
        NSLog(@"Se ha producido error al grabar: %@", [error localizedDescription]);
    }
} 

La clase NSFetchedResultsController (una de esas cosas que debemos agradecer al obsoleto iOS 3.0) es la que se va a encargar de mostrar los datos en la pantalla del iPhone. Nosotros, lo único que hemos hecho a la plantilla que ha creado XCode es cambiar los nombres de la entidad y los atributos para adaptarlos a los de nuestra base de datos en servidor. El código fuente de este proyecto lo podréis encontrar en GitHub. Es ligeramente diferente a lo que se ha visto aquí porque incluye el borrado de elementos desde la propia aplicación y código para gestionar la descarga de registros ya grabados.