¿Qué encontrarás en este post?
ToggleGrand Central Dispatch GCD y uso de bloques para multitarea
Esta aplicación con Grand Central Dispatch GCD quizás sea la más común e importante de los bloques en Cocoa y Cocoa Touch.Grand Central Dispatch
Grand Central Dispatch GCD es una API C para multitarea usando hebras. De cara al usuario es muy sencilla: simplemente pones bloques en una cola que van siendo ejecutados en otra hebra. Las colas más comunes son las que ejecutan los bloques en serie (uno detrás del otro), como una cola verdadera. También las hay que ejecutan todos los bloques en paralelo, aunque son tema para otro artículo. Aunque las CPU de los dispositivos iOS no son (a fecha de hoy, Abril de 2011) multi-nucleo, el uso de hebras tiene muchas ventajas. Cuando una cola s queda bloqueada, las demás siguen funcionando sin problemas. Por lo tanto, toda tarea que consuma mucha CPU o que pueda bloquearse (conexiones de red) debería de ser ejecutada en otra hebra. De esta forma, la cola principal (y su hebra) no se verá bloqueada y el GUI seguirá funcionando sin retrasos ni bloqueos.Principales funciones de la API
Crear y liberar colas
-
dispatch_queue_t dispatch_queue_create(const char *label, NULL); //crear nueva cola
-
void dispatch_release(dispatch_queue_t); //liberar dicha cola
Poner bloques en la cola
typedef void (^dispatch_block_t) (void); void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);Obtener la cola actual y la principal
dispatch_queue_t dispatch_get_current_queue(); dispatch_queue_t dispatch_get_main_queue(); Si vamos a crear un bloque que va a ejecutar código de UIKit o CoreData, hay que asegurarse de ejecutarlo en la cola y hebra principal, ya que ninguna de las frameworks es segura para multitarea.Ejemplo de bloques y Grand Central Dispatch
Código que prepara una jerarquía de vistas para enseñar una imagen bajada de la red. Consiste en una UIScrollView, UIImageView y una UIActivityIndicatorView. Esta última no es una subvista de la UIScrollView, para que esté siempre en el centro indicando que “estamos en ello”. El código que baja la imagen en paralelo es EXACTAMENTE igual al que la baja en serie. La única modificación que tenemos que hacer es rodearlo con un dispatch_async(xxx, ^{ …. }). Ojo, que como parte del código usa UIKit, éste a su vez se ejecuta dentro de otro bloque (un bloque dentro de otro) que se asigna a la cola principal.-
–(void) viewWillAppear:(BOOL)animated{
-
[superviewWillAppear:animated];
-
NSString *const kURLString = @“http://www.destination360.com/north-america/us/idaho/images/s/idaho-sawtooth-mountains.jpg”;
-
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
-
self.scrollView.backgroundColor = [UIColor blackColor];
-
self.imageView = [[UIImageView alloc] initWithFrame:self.scrollView.bounds];
-
[self.scrollView addSubview:self.imageView];
-
[self.view addSubview:self.scrollView];
-
self.activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
-
self.activityView.hidesWhenStopped = YES;
-
[self.activityView startAnimating];
-
[self.view addSubview:self.activityView];
-
self.activityView.center = self.scrollView.center;
-
dispatch_queue_t downloader = dispatch_queue_create(“downloader”, NULL);
-
dispatch_async(downloader, ^{
-
// Potentially blocking stuff goes in another thread
-
initWithContentsOfURL:[NSURLURLWithString: kURLString]
-
options:NSDataReadingMapped
-
error: &err] autorelease];
-
dispatch_async(dispatch_get_main_queue(), ^{
-
if (!err) {
-
// UI stuff cannot be done outside the Main Thread/Queue
-
UIImage *img = [UIImageimageWithData:imgBlob];
-
self.imageView.image = img;
-
self.imageView.frame = CGRectMake(0, 0, img.size.width, img.size.height);
-
self.scrollView.contentSize = img.size;
-
[self.activityViewstopAnimating];
-
}else{
-
NSLog(@“%@”,[err localizedDescription]);
-
}
-
[self.activityViewstopAnimating];
-
});
-
});
-
dispatch_release(downloader);
-
}
Adaptar tu API al asincronismo
Si tenemos un objeto con una propiedad image con un getter-(UIImage *) image;y queremos que el obtener dicha imagen de la red sea en paralelo, tendremos que pensar en cómo modificar nuestro getter. Tal como está no funcionará, ya que retornará de inmediato, ¡antes de que se haya obtenido los datos de la red! Podríamos crear un método que inicie la descarga y una notificación, o un método de delegado, que nos avise cuando ha terminado. Sin embargo, los bloques nos dan una alternativa infinitamente más sencilla, al usarlos como gestores de finalización. Creamos un método que acepta un bloque que se ejecutará al terminar:
-(void)processImageDataWithBlock: (void (^)(NSData* imageData)) imageProcessorBlock;o bien, incluyendo también un gestor de error:
-(void)processImageDataWithBlock: (void (^)(NSData* imageData)) imageProcessorBlock
andErrorWithBlock:(void (^) (void)) errorBlock;
Contexto en el que se debe ejecutar un gestor de error o finalización
¡OJO! Al implementar dicho método, hay que ejecutar el código de descarga en una hebra a parte, pero ¡los gestores han ser ejecutados el la hebra actual! Como no sabemos si incluyen código UIKit o CoreData (y es muy probable que sea así), podríamos causar graves problemas (deadlocks, race conditions) si lo ejecutamos en una hebra que no sea la actual. Para obtener la cola actual se usa la función:dispatch_queue_t dispatch_get_current_queue();Y para implementar el método, se debe usar la técnica de un bloque dentro del otro ya citada.