lunes, 11 de febrero de 2013

Objective-C: ejecución sincrónica mediante semáforos

En Objective-C, o para ser más precisos en iOS, la norma es que cuando una tarea lleva tiempo, la misma se ejecuta de forma asincrónica mediante el uso de delegates y call-backs.

Por ejemplo, si queremos obtener el contenido de una página web, usamos NSURLConnection. El método start de dicha clase, inicia la operación y termina inmediatamente, antes de comenzar a recibir datos. Los datos llegan a través de los métodos del protocolo NSURLConnectionDelegate, como por ejemplo, connection:didReceiveData:.

Esto presenta un problema para la implementación de las clases estándar de GeneXus, ya que por ejemplo cuando hacemos
&httpClient.execute('GET', 'http://www.google.com')
&status = &httpClient.statusCode
esperamos que al volver de la llamada la variable &status tenga la información correspondiente.

La solución que encontramos para que la ejecución se realice de forma sincrónica, es mediante el uso de semáforos.

Los semáforos son parte de una tecnología llamada Grand Central Dispatch, que está disponible para iOS a partir de la versión 4.0.

La forma de implementar esto es la siguiente:
  1. cuando se llama al método que queremos que ejecute de forma sincrónica, se crea un nuevo semáforo mediante una llamada a la función dispatch_semaphore_create:
    _loadingSemaphore = dispatch_semaphore_create(0);
  2. la operación asincrónica se debe iniciar en otro thread (en particular hay operaciones que necesitan ejecutarse en el main thread), de forma que no haya bloqueos. En el ejemplo del HTTPClient, sería así:
    dispatch_sync(dispatch_get_main_queue(), ^{
      _conn = [NSURLConnection connectionWithRequest:request delegate:self];
      [_conn start];
    });
  3. cuando termina la operación asincrónica, se libera el semáforo de forma que los demás métodos puedan ejecutar:
    dispatch_semaphore_signal(_loadingSemaphore);
  4. mientras tanto, los demás métodos que necesitan que haya terminado la operación para poder ejecutar, como por ejemplo el statusCode, se bloquean hasta que termine la ejecución, luego realizan la tarea que necesitan y por último mandan un nuevo signal al semáforo por si hay alguna otra tarea esperando:
    dispatch_semaphore_wait(_loadingSemaphore, DISPATCH_TIME_FOREVER);
    block();
    dispatch_semaphore_signal(_loadingSemaphore);
Esta forma de implementar la ejecución sincrónica tiene además una ventaja: solo espera cuando es necesario. En el ejemplo, ejecutábamos el método execute e inmediatamente le pedíamos el statusCode, por lo que va a quedar esperando. Pero si antes de pedir el statusCode hacemos otra cosa, como por ejemplo ejecutar un procedimiento, puede ser que al terminar el procedimiento ya hayan venido los datos, y por lo tanto el statusCode no espera.

No hay comentarios.:

Publicar un comentario