Objective-C Sucintamente Bloques

Bloques son en realidad una extensión del lenguaje de programación C, pero son muy utilizados por los marcos de trabajo de Objective-C de Apple. Son similares a las lambdas de C # en que le permiten definir un bloque de sentencias en línea y pasarlo a otras funciones como si fuera un objeto..

Procesando datos con funciones vs. realizando acciones arbitrarias con bloques

Los bloques son increíblemente convenientes para definir métodos de devolución de llamada, ya que le permiten definir la funcionalidad deseada en el punto de invocación en lugar de en otro lugar en su programa. Además, los bloques se implementan como cierres (al igual que las lambdas en C #), que permite capturar el estado local que rodea el bloque sin ningún trabajo adicional.


Creando Bloques

La sintaxis de bloque puede ser un poco inquietante en comparación con la sintaxis de Objective-C que hemos estado usando a lo largo de este libro, así que no se preocupe si le lleva un tiempo sentirse cómodo con ellos. Comenzaremos mirando un ejemplo simple:

^ (int x) return x * 2; ;

Esto define un bloque que toma un parámetro entero., X, y devuelve ese valor multiplicado por dos. Aparte del caret (^), esto se asemeja a una función normal: tiene una lista de parámetros entre paréntesis, un bloque de instrucciones entre llaves y un valor de retorno (opcional). En C #, esto se escribe como:

x => x * 2;

Pero, los bloques no se limitan a expresiones simples, pueden contener un número arbitrario de declaraciones, como una función. Por ejemplo, puede agregar un NSLog () llamar antes de devolver un valor:

^ (int x) NSLog (@ "A punto de multiplicar% i por 2.", x); devuelve x * 2; ;

Bloques sin parámetros

Si su bloque no toma ningún parámetro, puede omitir la lista de parámetros por completo:

^ NSLog (@ "Este es un bloque bastante artificial."); NSLog (@ "Simplemente genera estos dos mensajes."); ;

Uso de bloques como devoluciones de llamada

Por sí solo, un bloque no es tan útil. Normalmente, los pasará a otro método como una función de devolución de llamada. Esta es una característica del lenguaje muy poderosa, ya que le permite tratar funcionalidad como un parámetro, en lugar de limitarse a datos. Puede pasar un bloque a un método como lo haría con cualquier otro valor literal:

[unObject doSomethingWithBlock: ^ (int x) NSLog (@ "Multiplicando% i por dos"); devuelve x * 2; ];

los hacer algo con el bloqueo: La implementación puede ejecutar el bloque como si fuera una función, lo que abre la puerta a muchos nuevos paradigmas organizacionales..

Como un ejemplo más práctico, echemos un vistazo a la sortUsingComparator: método definido por NSMutableArray. Esto proporciona la misma funcionalidad que la sortedArrayUsingFunction: método que utilizamos en el Capítulo de tipos de datos, excepto que usted define el algoritmo de clasificación en un bloque en lugar de una función completa:

Ejemplo de código incluido: SortUsingBlock

#importar  int main (int argc, const char * argv []) @autoreleasepool NSMutableArray * numbers = [NSMutableArray arrayWithObjects: [NSNumber numberWithFloat: 3.0f], [NSNumber numberWithFloat: 5.5f], [NSNumber numberWithFloat: 1.0f], NSNumber numberWithFloat: 12.2f], nil]; [números sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) float number1 = [obj1 floatValue]; float number2 = [obj2 floatValue]; si (numero 1 < number2)  return NSOrderedAscending;  else if (number1 > número2) return NSOrderedDescending;  else return NSOrderedSame; ]; para (int i = 0; i<[numbers count]; i++)  NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]);   return 0; 

Nuevamente, este es un orden ascendente directo, pero poder definir el algoritmo de clasificación en el mismo lugar que la invocación de la función es más intuitivo que tener que definir una función independiente en otro lugar del programa. También tenga en cuenta que puede declarar variables locales en un bloque tal como lo haría en una función.

Los marcos estándar de Objective-C utilizan este patrón de diseño para todo, desde la clasificación, la enumeración y la animación. De hecho, incluso podría reemplazar el for-loop en el último ejemplo con NSArrayes enumerateObjectsUsingBlock: método, como se muestra aquí:

[ordenadoNúmeros enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); if (idx == 2) // Deja de enumerar al final de esta iteración. * parada = SÍ; ];

los obj parámetro es el objeto actual, idx es el índice actual, y *detener Es una forma de salir prematuramente de la enumeración. Configurando el *detener puntero a le dice al método que deje de enumerar después de la iteración actual. Todo este comportamiento está especificado por el enumerateObjectsUsingBlock: método.

Si bien la animación es un poco fuera de tema para este libro, todavía vale la pena una breve explicación para ayudar a entender la utilidad de los bloques.. Vista Es una de las clases más utilizadas en la programación de iOS. Es un contenedor gráfico genérico que le permite animar su contenido a través de animateWithDuration: animaciones: método. El segundo parámetro es un bloque que define el estado final de la animación, y el método determina automáticamente cómo animar las propiedades usando el primer parámetro. Esta es una forma elegante y fácil de usar para definir transiciones y otros comportamientos basados ​​en temporizadores. Vamos a discutir las animaciones con mucho más detalle en la próxima iOS sucintamente libro.


Almacenar y ejecutar bloques

Además de pasarlos a los métodos, los bloques también se pueden almacenar en variables para su uso posterior. Este caso de uso sirve esencialmente como una forma alternativa de definir funciones:

#importar  int main (int argc, const char * argv []) @autoreleasepool int (^ addIntegers) (int, int); addIntegers = ^ (int x, int y) return x + y; ; int result = addIntegers (24, 18); NSLog (@ "% i", resultado);  devuelve 0; 

Primero, vamos a inspeccionar la sintaxis para declarar variables de bloque: int (^ addIntegers) (int, int). El nombre de esta variable es simplemente addIntegers (sin el caret). Esto puede ser confuso si no ha usado bloques por mucho tiempo. Es útil pensar en el cursor como la versión del bloque del operador de desreferencia (*). Por ejemplo, un puntero llamado addIntegers sería declarado como * addIntegers-igualmente, un bloquear del mismo nombre se declara como ^ addIntegers. Sin embargo, tenga en cuenta que esto es simplemente una similitud superficial.

Además del nombre de la variable, también debe declarar todos los metadatos asociados con el bloque: el número de parámetros, sus tipos y el tipo de retorno. Esto permite al compilador imponer la seguridad de tipos con variables de bloque. Tenga en cuenta que el caret es no parte del nombre de la variable, solo se requiere en la declaración.

A continuación, utilizamos el operador de asignación estándar (=) para almacenar un bloque en la variable. Por supuesto, los parámetros del bloque ((int x, int y)) debe coincidir con los tipos de parámetros declarados por la variable ((int, int)). También se requiere un punto y coma después de la definición del bloque, al igual que una asignación de variable normal. Una vez que se ha llenado con un valor, la variable se puede llamar como una función: addIntegers (24, 18).

Variables de bloque sin parámetro

Si su bloque no toma ningún parámetro, debe declararlo explícitamente en la variable colocando vacío en la lista de parámetros:

void (^ contrived) (void) = ^ NSLog (@ "Este es un bloque bastante artificial."); NSLog (@ "Simplemente genera estos dos mensajes."); ; ideado ();

Trabajando con variables

Las variables dentro de los bloques se comportan de la misma manera que en las funciones normales. Puede crear variables locales dentro del bloque, acceder a los parámetros pasados ​​al bloque y usar variables y funciones globales (por ejemplo,., NSLog ()). Pero, los bloques también tienen acceso a variables no locales, ¿Cuáles son las variables del ámbito léxico adjunto?.

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42

En este caso, valor inicial se considera una variable no local dentro del bloque porque se define fuera del bloque (no localmente, en relación con el bloque). Por supuesto, el hecho de que las variables no locales sean de solo lectura implica que no puede asignarlas:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) initialValue = 5; // Esto lanzará un error de compilación. devuelve initialValue + x; ;

Tener acceso a las variables circundantes (no locales) es un gran problema cuando se usan bloques en línea como parámetros de método. Proporciona una manera conveniente de representar cualquier estado requerido dentro del bloque.

Por ejemplo, si estaba animando el color de un componente de la interfaz de usuario y el color de destino se calculó y almacenó en una variable local antes de la definición del bloque, simplemente podría usar la variable local dentro del bloque, sin necesidad de trabajo adicional. Si no tuviera acceso a variables no locales, habría pasado el valor del color como un parámetro de bloque adicional. Cuando su funcionalidad de devolución de llamada se basa en una gran parte del estado circundante, esto puede ser muy engorroso.

Los bloques son cierres

Sin embargo, los bloques no solo tienen acceso a las variables no locales, también aseguran que esas variables Nunca Cambio, no importa cuándo o dónde se ejecute el bloque. En la mayoría de los lenguajes de programación, esto se llama cierre.

Los cierres funcionan al hacer una copia constante de solo lectura de cualquier variable no local y almacenarlas en un tabla de referencia Con las afirmaciones que componen el propio bloque. Estos valores de solo lectura se usan cada vez que se ejecuta el bloque, lo que significa que incluso si la variable original no local cambia, se garantiza que el valor utilizado por el bloque será el mismo que cuando se definió el bloque.

Almacenamiento de variables no locales en una tabla de referencia

Podemos ver esto en acción asignando un nuevo valor a la valor inicial variable del ejemplo anterior:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Todavía 42.

No importa donde llames addToInitialValue (), la valor inicial utilizado por el bloque será siempre ser 32, porque eso es lo que era cuando fue creado. Para todos los efectos, es como si el valor inicial La variable se transformó en un valor literal dentro del bloque..

Entonces, la utilidad de los bloques es doble:

  1. Te permiten representar la funcionalidad como un objeto..
  2. Te permiten representar información del estado junto con esa funcionalidad.

Toda la idea detrás de la funcionalidad de encapsulación en un bloque es poder usarlo luego en el programa. Los cierres permiten asegurar un comportamiento predecible cuando un bloque se ejecuta congelando el estado circundante. Esto los convierte en un aspecto integral de la programación de bloques..

Variables de bloque mutables

En la mayoría de los casos, capturar el estado con cierres es intuitivamente lo que cabría esperar de un bloque. Hay, sin embargo, tiempos que requieren el comportamiento opuesto.. Variables de bloque mutables son variables no locales que se designan de lectura-escritura en lugar de las predeterminadas de solo lectura. Para hacer que una variable no local sea mutable, debe declararla con la __bloquear modificador, que crea un enlace directo entre la variable utilizada fuera del bloque y la que se usa dentro del bloque. Esto abre la puerta al uso de bloques como iteradores, generadores y cualquier otro tipo de objeto que procesa el estado..

Creando un enlace directo con una variable de bloque mutable.

El siguiente ejemplo le muestra cómo hacer que una variable no local sea mutable:

#importar  #import "Person.h" int main (int argc, const char * argv []) @autoreleasepool __block NSString * name = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Cambiar% @ a Frank", nombre); nombre = @ "Frank"; ; NSLog (@ "% @", nombre); // Dave // ​​Cambiarlo desde dentro del bloque. generateRandomName (); // Cambiando a Dave por Frank. NSLog (@ "% @", nombre); // Frank // Cambiarlo desde fuera del bloque. nombre = @ "Heywood"; generateRandomName (); // Cambiando Heywood a Frank.  devuelve 0; 

Esto se ve casi exactamente igual que en el ejemplo anterior, con dos diferencias muy significativas. Primero, lo no local. nombre variable puede Ser asignado desde dentro del bloque. Segundo, cambiando la variable fuera del bloque hace actualizar el valor utilizado dentro del bloque. Incluso es posible crear varios bloques que manipulan la misma variable no local.

La única advertencia para usar el __bloquear modificador es que no poder ser utilizado en matrices de longitud variable.

Definición de métodos que aceptan bloques

Podría decirse que crear métodos que acepten bloques es más útil que almacenarlos en variables locales. Te da la oportunidad de agregar tus propios enumerateObjectsUsingBlock:-Métodos de estilo para clases personalizadas..

Considere la siguiente interfaz para el Persona clase:

// Person.h @interface Persona: NSObject @property int age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) actividad; @fin

los vacío (^) (int) código es el tipo de datos para el bloque que desea aceptar. En este caso, aceptaremos un bloque sin valor de retorno y un único parámetro entero. Tenga en cuenta que, a diferencia de las variables de bloque, esto no requiere un nombre para el bloque, solo un sin adornos ^ personaje.

Ahora tiene todas las habilidades necesarias para crear métodos que acepten bloques como parámetros. Una implementación simple para el Persona La interfaz que se muestra en el ejemplo anterior podría ser algo como:

// Person.m #import "Person.h" @implementation Person @synthesize age = _age; - (void) celebrateBirthdayWithBlock: (void (^) (int)) activity NSLog (@ "It's a party !!!"); actividad (self.age);  @final

A continuación, puede pasar una actividad personalizable para realizar en un Persona's cumpleaños como así:

// main.m int main (int argc, const char * argv []) @autoreleasepool Person * dave = [[alloc As]]]; dave.age = 37; [dave celebrateBirthdayWithBlock: ^ (int age) NSLog (@ "¡Woot! Me estoy convirtiendo% i", edad + 1); ];  devuelve 0; 

Debería ser evidente que el uso de bloques como parámetros es infinitamente más flexible que los tipos de datos estándar que hemos estado utilizando hasta este capítulo. Puedes decirle una instancia a hacer algo, en lugar de simplemente procesar datos.


Resumen

Los bloques le permiten representar sentencias como objetos Objective-C, lo que le permite pasar arbitrariamente comportamiento a una función en lugar de limitarse a datos. Esto es útil para todo, desde iterar sobre una secuencia de objetos hasta animar componentes de IU. Los bloques son una extensión versátil del lenguaje de programación C, y son una herramienta necesaria si planeas trabajar mucho con los marcos estándar de iOS. En este capítulo, aprendimos cómo crear, almacenar y ejecutar bloques, y aprendimos acerca de las complejidades de los cierres y la __bloquear modificador de almacenamiento. También discutimos algunos paradigmas de uso comunes para bloques..

Así concluye nuestro viaje a través de Objective-C. Hemos cubierto todo, desde la sintaxis básica hasta los tipos de datos centrales, clases, protocolos, propiedades, métodos, administración de memoria, manejo de errores e incluso el uso avanzado de bloques. Nos enfocamos más en las características del lenguaje que en crear aplicaciones gráficas, pero esto proporcionó una base sólida para el desarrollo de aplicaciones iOS. Por ahora, espero que te sientas muy cómodo con el lenguaje Objective-C.

Recuerde que Objective-C se basa en muchos de los mismos conceptos orientados a objetos que otros lenguajes OOP. Si bien solo mencionamos algunos patrones de diseño orientados a objetos en este libro, prácticamente todos los paradigmas organizacionales disponibles para otros idiomas también son posibles en Objective-C. Esto significa que puede aprovechar fácilmente su base de conocimientos orientada a objetos existente con las herramientas presentadas en los capítulos anteriores..


iOS sucintamente

Si está listo para comenzar a crear aplicaciones funcionales para iPhone y iPad, asegúrese de revisar la segunda parte de esta serie., iOS sucintamente. Esta guía práctica para el desarrollo de aplicaciones aplica todas las habilidades de Objective-C adquiridas de este libro a situaciones de desarrollo del mundo real. Recorreremos todos los marcos principales de Objective-C y aprenderemos cómo realizar tareas a lo largo del camino, que incluyen: configurar interfaces de usuario, capturar información, dibujar gráficos, guardar y cargar archivos, y mucho, mucho más.

Esta lección representa un capítulo de Objective-C Sucintamente, un libro electrónico gratuito del equipo de Syncfusion.