Objective-C Sucintamente Categorías y Extensiones

Las categorías son una característica del lenguaje Objective-C que le permiten agregar nuevos métodos a una clase existente, como las extensiones C #. Sin embargo, no confunda las extensiones C # con las extensiones Objective-C. Las extensiones de Objective-C son un caso especial de categorías que le permiten definir métodos que deben declararse en el bloque de implementación principal.

Estas son características poderosas que tienen muchos usos potenciales. Primero, las categorías hacen posible dividir la interfaz y la implementación de una clase en varios archivos, lo que proporciona una modularidad muy necesaria para proyectos más grandes. En segundo lugar, las categorías le permiten corregir errores en una clase existente (por ejemplo,., NSString) Sin necesidad de subclasificarlo. Tercero, proporcionan una alternativa efectiva a los métodos protegidos y privados que se encuentran en C # y otros lenguajes similares a Simula.


Categorías

UNA categoría es un grupo de métodos relacionados para una clase, y todos los métodos definidos en una categoría están disponibles a través de la clase como si estuvieran definidos en el archivo de interfaz principal. Como ejemplo, tome el Persona clase con la que hemos estado trabajando a lo largo de este libro. Si este fuera un gran proyecto., Persona puede tener docenas de métodos que van desde comportamientos básicos hasta interacciones con otras personas para verificar la identidad. La API podría requerir que todos estos métodos estén disponibles a través de una sola clase, pero es mucho más fácil para los desarrolladores mantener si cada grupo se almacena en un archivo separado. Además, las categorías eliminan la necesidad de volver a compilar toda la clase cada vez que se cambia un solo método, lo que puede ahorrar tiempo en proyectos muy grandes..

Echemos un vistazo a cómo se pueden usar las categorías para lograr esto. Comenzamos con una interfaz de clase normal y una implementación correspondiente:

// Person.h @interface Persona: NSObject @interface Persona: NSObject @property (solo lectura) NSMutableArray * friends; @property (copia) NSString * nombre; - (void) sayHello; - (vacío) diga adiós; @end // Person.m #import "Person.h" @implementation Person @synthesize name = _name; @synthesize friends = _friends; - (id) init self = [super init]; if (self) _friends = [[NSMutableArray alloc] init];  devuélvete a ti mismo;  - (void) sayHello NSLog (@ "Hola, dice% @.", _name);  - (void) sayGoodbye NSLog (@ "Goodbye, dice% @.", _name);  @final

Nada nuevo aquí, solo un Persona clase con dos propiedades (la amigos propiedad será utilizada por nuestra categoría) y dos métodos. A continuación, usaremos una categoría para almacenar algunos métodos para interactuar con otros Persona instancias. Cree un nuevo archivo, pero en lugar de una clase, use el Categoría Objective-C modelo. Utilizar Relaciones para el nombre de la categoría y Persona Para el Categoría en campo:

Creando la clase Persona + Relaciones

Como se esperaba, esto creará dos archivos: un encabezado para contener la interfaz y una implementación. Sin embargo, ambos se verán ligeramente diferentes a lo que hemos estado trabajando. Primero, echemos un vistazo a la interfaz:

// Persona + Relaciones.h #importar  #import "Person.h" @interface Person (Relations) - (void) addFriend: (Person *) aFriend; - (void) removeFriend: (Person *) aFriend; - (void) sayHelloToFriends; @fin

En lugar de lo normal @interfaz declaración, incluimos el nombre de la categoría entre paréntesis después del nombre de la clase que estamos extendiendo. Un nombre de categoría puede ser cualquier cosa, siempre que no entre en conflicto con otras categorías para la misma clase. Una categoría expediente nombre debe ser el nombre de la clase seguido de un signo más, seguido del nombre de la categoría (por ejemplo,., Persona + Relaciones.h ).

Entonces, esto define la interfaz de nuestra categoría. Cualquier método que agreguemos aquí será agregado al original Persona clase en tiempo de ejecución Aparecerá como si el añadir amigo:, Eliminar amigo:, y sayHelloToFriends todos los métodos están definidos en Persona.h, pero podemos mantener nuestra funcionalidad encapsulada y mantenible. También tenga en cuenta que debe importar el encabezado de la clase original, Persona.h. La implementación de la categoría sigue un patrón similar:

// Person + Relations.m #import "Person + Relations.h" @implementation Person (Relations) - (void) addFriend: (Person *) aFriend [[self friends] addObject: aFriend];  - (void) removeFriend: (Person *) aFriend [[self friends] removeObject: aFriend];  - (void) sayHelloToFriends for (Person * friend in [self friends]) NSLog (@ "Hello there,% @!", [nombre de amigo]);   @final

Esto implementa todos los métodos en Persona + Relaciones.h. Al igual que la interfaz de la categoría, el nombre de la categoría aparece entre paréntesis después del nombre de la clase. El nombre de la categoría en la implementación debe coincidir con el de la interfaz..

Además, tenga en cuenta que no hay manera de definir propiedades adicionales o variables de instancia en una categoría. Las categorías deben remitirse a los datos almacenados en la clase principal (amigos en este caso).

También es posible anular la implementación contenida en Persona.m simplemente redefiniendo el método en Persona + Relaciones.m. Esto se puede usar para parchar una clase existente; sin embargo, no se recomienda si tiene una solución alternativa al problema, ya que no habría manera de anular la implementación definida por la categoría. Es decir, a diferencia de la jerarquía de clases, las categorías son una estructura organizativa plana: si implementa el mismo método en dos categorías separadas, es imposible que el tiempo de ejecución determine cuál usar.

El único cambio que debe hacer para usar una categoría es importar el archivo de encabezado de la categoría. Como puede ver en el siguiente ejemplo, la Persona clase tiene acceso a los métodos definidos en Persona.h Junto con los definidos en la categoría. Persona + Relaciones.h:

// main.m #import  #import "Person.h" #import "Person + Relations.h" int main (int argc, const char * argv []) @autoreleasepool Person * joe = [[alloc alloc] init]; joe.name = @ "Joe"; Persona * factura = [[Asignación de persona] init]; bill.name = @ "Bill"; Persona * mary = [[Asignación de persona] init]; mary.name = @ "Mary"; [joe sayHello]; [joe addFriend: bill]; [joe addFriend: mary]; [joe sayHelloToFriends];  devuelve 0; 

Y eso es todo lo que hay para crear categorías en Objective-C..

Métodos protegidos

Reiterar, todos Los métodos de Objective-C son públicos; no hay una construcción de lenguaje para marcarlos como privados o protegidos. En lugar de utilizar métodos protegidos "verdaderos", los programas de Objective-C pueden combinar categorías con el paradigma de interfaz / implementación para lograr el mismo resultado.

La idea es simple: declarar los métodos "protegidos" como una categoría en un archivo de encabezado separado. Esto le da a las subclases la capacidad de "inscribirse" a los métodos protegidos, mientras que las clases no relacionadas usan el archivo de cabecera "público" como de costumbre. Por ejemplo, tomar un estándar Enviar interfaz:

// Ship.h #import  @interface Ship: NSObject - (void) shoot; @fin

Como hemos visto muchas veces, esto define un método público llamado disparar. Declarar un protegido método, tenemos que crear una Enviar categoría en un archivo de encabezado dedicado:

// Ship_Protected.h #import  @interface Ship (Protected) - (void) prepareToShoot; @fin

Cualquier clase que necesite acceso a los métodos protegidos (a saber, la superclase y cualquier subclase) puede simplemente importar Ship_Protected.h. Por ejemplo, el Enviar La implementación debe definir un comportamiento predeterminado para el método protegido:

// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementation Ship BOOL _gunIsReady;  - (void) dispara if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = YES;  NSLog (@ "¡Disparando!");  - (void) prepareToShoot // Ejecuta alguna funcionalidad privada. NSLog (@ "Preparando el arma principal ...");  @final

Tenga en cuenta que si no hubiéramos importado Ship_Protected.h, esta prepareToShoot La implementación sería un método privado, como se discute en el Capítulo de métodos. Sin una categoría protegida, no habría forma de que las subclases accedan a este método. Vamos a la subclase Enviar Para ver cómo funciona esto. Lo llamaremos Nave de investigacion:

// ResearchShip.h #import "Ship.h" @interface ResearchShip: Ship - (void) extendTelescope; @fin

Esta es una interfaz de subclase normal, debería no Importe el encabezado protegido, ya que esto haría que los métodos protegidos estén disponibles para cualquier persona que importe ResearchShip.h, que es precisamente lo que estamos tratando de evitar. Finalmente, la implementación de la subclase importa los métodos protegidos y (opcionalmente) los anula:

// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation ResearchShip - (void) extendTelescope NSLog (@ "Extending the telescope");  // Reemplazar el método protegido - (vacío) prepareToShoot NSLog (@ "¡Oh dispara! ¡Necesitamos encontrar algunas armas!");  @final

Para hacer cumplir el estado protegido de los métodos en Ship_Protected.h, otras clases no tienen permitido importarlo. Solo importarán las interfaces "públicas" normales de la superclase y la subclase:

// main.m #import  #import "Ship.h" #import "ResearchShip.h" int main (int argc, const char * argv []) @autoreleasepool Ship * genericShip = [[Ship alloc] init]; [disparar genérico de la nave]; Ship * discoveryOne = [[ResearchShip alloc] init]; [descubrimiento de un disparo];  devuelve 0; 

Ya que ninguno main.m, Nave.h, ni ResearchShip.h Importa los métodos protegidos, este código no tendrá acceso a ellos. Intenta agregar un [discoveryOne prepareToShoot] método-arrojará un error de compilación, ya que el prepareToShoot la declaración no se encuentra en ninguna parte.

Para resumir, los métodos protegidos se pueden emular colocándolos en un archivo de encabezado dedicado e importando ese archivo de encabezado en los archivos de implementación que requieren acceso a los métodos protegidos. Ningún otro archivo debe importar el encabezado protegido.

Si bien el flujo de trabajo que se presenta aquí es una herramienta organizativa completamente válida, tenga en cuenta que Objective-C nunca tuvo la intención de soportar métodos protegidos. Piense en esto como una forma alternativa de estructurar un método Objective-C, en lugar de un reemplazo directo de los métodos protegidos de C # / Simula. A menudo es mejor buscar otra forma de estructurar sus clases en lugar de obligar a su código de Objective-C a actuar como un programa de C #.

Advertencias

Uno de los mayores problemas con las categorías es que no puede anular de manera confiable los métodos definidos en categorías para la misma clase. Por ejemplo, si ha definido un añadir amigo: clase en Persona (Relaciones) y luego decidió cambiar el añadir amigo: implementación a través de Persona (Seguridad) En esta categoría, no hay forma de que el tiempo de ejecución sepa qué método debería usar, ya que las categorías son, por definición, una estructura organizativa plana. Para este tipo de casos, debe volver al paradigma de subclasificación tradicional.

Además, es importante tener en cuenta que una categoría no puede agregar variables de instancia. Esto significa que no puede declarar nuevas propiedades en una categoría, ya que solo se podrían sintetizar en la implementación principal. Además, aunque técnicamente una categoría tiene acceso a las variables de instancia de sus clases, es una mejor práctica acceder a ellas a través de su interfaz pública para proteger a la categoría de posibles cambios en el archivo de implementación principal.


Extensiones

Extensiones (también llamado extensiones de clase) son un tipo especial de categoría que requiere que sus métodos se definan en el principal Bloque de implementación para la clase asociada, a diferencia de una implementación definida en una categoría. Esto se puede usar para anular los atributos de propiedad declarados públicamente. Por ejemplo, a veces es conveniente cambiar una propiedad de solo lectura a una propiedad de lectura y escritura dentro de la implementación de una clase. Considere la interfaz normal para un Enviar clase:

Ejemplo de código incluido: Extensiones

// Ship.h #import  #import "Person.h" @interface Ship: NSObject @property (strong, readonly) Person * captain; - (id) initWithCaptain: (Person *) captain; @fin

Es posible anular el @propiedad definición dentro de una extensión de clase. Esto le da la oportunidad de volver a declarar la propiedad como leer escribir en el archivo de implementación. Sintácticamente, una extensión parece una declaración de categoría vacía:

// Ship.m #import "Ship.h" // La extensión de clase. @interface Ship () @property (strong, readwrite) Person * captain; @end // La implementación estándar. @implementación Ship @synthesize captain = _captain; - (id) initWithCaptain: (Person *) captain self = [super init]; if (self) // Esto funcionará debido a la extensión. [auto setCaptain: captain];  devuélvete a ti mismo;  @final

Nota la () añadido al nombre de la clase después de la @interfaz directiva. Esto es lo que lo marca como una extensión en lugar de una interfaz normal o una categoría. Cualquier propiedad o método que aparezca en la extensión. debe Ser declarado en el bloque principal de implementación para la clase. En este caso, no estamos agregando ningún campo nuevo, estamos anulando uno existente. Pero a diferencia de las categorías, las extensiones. puede agregar variables de instancia adicionales a una clase, por lo que podemos declarar propiedades en una extensión de clase pero no una categoría.

Porque hemos vuelto a declarar la capitán propiedad con un leer escribir atributo, el initWithCaptain: método puede utilizar el setCaptain: Accesorio en sí mismo. Si tuviera que eliminar la extensión, la propiedad volvería a su estado de solo lectura y el compilador se quejaría. Clientes usando el Enviar no se supone que la clase importe el archivo de implementación, por lo que capitán la propiedad seguirá siendo de solo lectura.

#importar  #import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * heywood = [[alloc alloc] init]; heywood.name = @ "Heywood"; Ship * discoveryOne = [[Ship alloc] initWithCaptain: heywood]; NSLog (@ "% @", [discoveryOne captain] .name); Persona * dave = [[Asignación de persona] init]; dave.name = @ "Dave"; // Esto NO funcionará porque la propiedad todavía es de solo lectura. [discoveryOne setCaptain: dave];  devuelve 0; 

Métodos privados

Otro caso de uso común para las extensiones es para declarar métodos privados. En el capítulo anterior, vimos cómo se pueden declarar los métodos privados simplemente agregándolos en cualquier lugar del archivo de implementación. Pero, antes de Xcode 4.3, este no era el caso. La forma canónica de crear un método privado era declararlo hacia adelante usando una extensión de clase. Echemos un vistazo a esto alterando ligeramente la Enviar encabezado del ejemplo anterior:

// Ship.h #import  @interface Ship: NSObject - (void) shoot; @fin

A continuación, vamos a recrear el ejemplo que usamos cuando discutimos métodos privados en el Capítulo de métodos. En lugar de simplemente añadir el privado prepareToShoot Método para la implementación, necesitamos declararlo en una extensión de clase..

// Ship.m #import "Ship.h" // La extensión de clase. @interface Ship () - (void) prepareToShoot; @end // El resto de la implementación. @implementation Ship BOOL _gunIsReady;  - (void) dispara if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = YES;  NSLog (@ "¡Disparando!");  - (void) prepareToShoot // Ejecuta alguna funcionalidad privada. NSLog (@ "Preparando el arma principal ...");  @final

El compilador garantiza que los métodos de extensión se implementen en el bloque de implementación principal, por lo que funciona como una declaración hacia adelante. Sin embargo, debido a que la extensión está encapsulada en el archivo de implementación, otros objetos nunca deberían saberlo, lo que nos brinda otra forma de emular métodos privados. Si bien los compiladores más nuevos le ahorran este problema, sigue siendo importante entender cómo funcionan las extensiones de clase, ya que ha sido una forma común de aprovechar los métodos privados en los programas de Objective-C hasta hace muy poco tiempo..


Resumen

Este capítulo cubrió dos de los conceptos más únicos en el lenguaje de programación de Objective-C: categorías y extensiones. Las categorías son una forma de extender la API de las clases existentes, y las extensiones son una forma de agregar necesario Métodos a la API fuera del archivo de interfaz principal. Ambos fueron diseñados inicialmente para aliviar la carga de mantener grandes bases de código.

El siguiente capítulo continúa nuestro viaje a través de las estructuras organizativas de Objective-C. Aprenderemos cómo definir un protocolo, que es una interfaz que puede ser implementada por una variedad de clases.

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