Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. iOS SDK
Code

Objective-C Sucintamente: Categorías y Extensiones

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Objective-C Succinctly.
Objective-C Succinctly: Methods
Objective-C Succinctly: Protocols

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

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 la necesidad de subclasificarlo. En tercer lugar, 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 la clase Person con la que hemos estado trabajando a lo largo de este libro. Si se tratara de un proyecto grande, Person 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 ahorrarle tiempo a 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:

Nada nuevo aquí: solo una clase Person con dos propiedades (la propiedad friends 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 otras instancias de Person. Cree un nuevo archivo, pero en lugar de una clase, use la plantilla Categoría de Objective-C. Utilice Relaciones para el nombre de la categoría y Person para el campo Category en:

Figure 28 Creating the PersonRelations class

Creando la clase Person+Relations

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:

En lugar de la declaración normal @interface, 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. El nombre del archivo de una categoría debe ser el nombre de la clase seguido de un signo más, seguido del nombre de la categoría (por ejemplo, Person+Relations.h).

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

Esto implementa todos los métodos en Person+Relations.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 (friends en esta instancia).

También es posible anular la implementación contenida en Person.m simplemente redefiniendo el método en Person+Relations.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 clase Person tiene acceso a los métodos definidos en Person.h junto con los definidos en la categoría Person+Relations.h:

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

Métodos protegidos

Para 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, tome una interfaz Ship estándar:

Como hemos visto muchas veces, esto define un método público llamado shoot. Para declarar un método protegido, necesitamos crear una categoría Ship en un archivo de encabezado dedicado:

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, la implementación de Ship debería definir un comportamiento predeterminado para el método protegido:

Tenga en cuenta que si no hubiéramos importado Ship_Protected.h, esta implementación de prepareToShoot sería un método privado, como se explica en el capítulo Methods. Sin una categoría protegida, no habría forma de que las subclases accedan a este método. Subclasificamos Ship para ver cómo funciona esto. Lo llamaremos ResearchShip:

Esta es una interfaz de subclase normal: no debería importar el encabezado protegido, ya que haría que los métodos protegidos estén disponibles para cualquiera 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:

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

Dado que ni main.m, Ship.h, ni ResearchShip.h importan los métodos protegidos, este código no tendrá acceso a ellos. Intente agregar un método [discoveryOne prepareToShoot]: lanzará un error de compilación, ya que la declaración prepareToShoot 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 admitir 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 definió una clase addFriend: en Person(Relations) y luego decidió cambiar la implementación addFriend: a través de una categoría Person(Security), no hay forma de que el tiempo de ejecución sepa qué método debe 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

Las Extensions (también llamadas extensiones de clase) son un tipo especial de categoría que requiere que sus métodos se definan en el bloque de implementación main para la clase asociada, en lugar 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 una clase Ship:

Ejemplo de código incluido: Extensiones

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

Tenga en cuenta que () se agrega al nombre de la clase después de la directiva @interface. 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 declararse en el bloque de implementación principal de 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 pueden agregar variables de instancia adicionales a una clase, por lo que podemos declarar propiedades en una extensión de clase pero no en una categoría.

Debido a que volvimos a declarar la propiedad captain con un atributo readwrite, el método initWithCaptain: puede usar setCaptain: accessor 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. Los clientes que utilizan la clase Ship no deben importar el archivo de implementación, por lo que la propiedad captain seguirá siendo de solo lectura.

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 modificando ligeramente el encabezado Ship del ejemplo anterior:

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

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.


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 métodos requeridos a la API fuera del archivo de interfaz principal. Ambos de estos 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.
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.