Bienvenido de vuelta a Objective-C desde Cero, en el capítulo anterior estuvimos hablando de clases, mensajes, apuntadores y objetos.
En este capítulo seguiremos hablando de orientación a objetos. Esta vez explicaré como agregar atributos y propiedades a nuestras clases, como crear interfaces (protocolos) y como extender la funcionalidad de una clase mediante el uso de categorías.
¡Alerta!
En este capítulo seguiremos trabajando sobre el proyecto del capítulo anterior. Si no venías siguiendo la serie, puedes clonarlo del siguiente repo de GitHub. Si quieres leer en capitulo anterior, puedes visitarlo aquí
Comencemos entonces.
Atributos de un clase
Los atributos o ivars (como se llaman en objective-c) proporcionan un poderoso método de asociar información declarativa. Una vez asociada a una entidad, el atributo puede ser consultado en tiempo de ejecución y utilizarse en cualquier número de maneras.
Para agregar atributos a una clase solo tenemos que declarar variables entre llaves en el encabezado.
A continuación vemos un ejemplo de como quedaría el encabezado de nuestra clase person luego de agregarle atributos.
@interface Person : NSObject { NSString *name; NSDate *birthDate; float height; float weight; } - (void)walk; - (void)jumpHeight:(float)centimeters; - (void)runDistance:(float)meters withSpeed:(float)speed; + (int)age; @end
En este ejemplo le dimos un nombre, fecha de nacimiento, altura y peso a la persona.
Encapsulamiento
Existen tres palabras clave para hacer uso de encapsulamiento: @public, @private y @protected. Todas las variables que estén declaradas debajo de estas palabras serán afectadas por estos modificadores. Por defecto el compilador tomará como protected todas las que no estén afectadas por algún modificador explícitamente.
Para los efectos de este ejemplo aplicaremos estos modificadores de la siguiente manera:
@interface Person : NSObject { NSString *name; @public NSDate *birthDate; @private float height; @protected float weight; }
Name por defecto es protected.
Propiedades
Las propiedades son una manera conveniente de generar getter y setters para nuestros atributos.
Nuestra clase quedaría de la siguiente manera:
@interface Person : NSObject { NSString *name; @public NSDate *birthDate; @private float height; @protected float weight; } @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSDate *birthDate; @property (nonatomic, assign, readonly) float height; @property (nonatomic, assign) float weight; @property (nonatomic, strong) NSString *lastName; @property (nonatomic, assign, getter = isBusinessPerson) BOOL businessPerson; - (void)walk; - (void)jumpHeight:(float)centimeters; - (void)runDistance:(float)meters withSpeed:(float)speed; + (int)age; @end
Las propiedades no tienen que estar siempre respaldadas por una variable de instancia (ivar). Por ejemplo, las propiedades lastName y businessPerson no tienen ivars que las respalden.
Objective-C no provee una manera de poder hacer métodos public, private o protected, y como las propiedades en el fondo son métodos get y set, por ende, tampoco se puede para las propiedades.
Agregando métodos de acceso
Las propiedades tienen atributos propios que definen su comportamiento. Por defecto todas son readwrite, significa que se les puede asignar un valor desde fuera. Lo contrario es readonly, a este tipo solo se le puede cambiar su valor a través de la variable de instancia (ivar) que la respalda.
También se pueden declarar metodos de acceso personalizado como tenemos en la propiedad businessPerson:
@property (nonatomic, assign, getter = isBusinessPerson) BOOL businessPerson;
En este caso el compilador generará un método get llamado isBusinessPerson y no businessPerson.
Atomic y Nonatomic
Por defecto todas las propiedades son Atomic. Esto significa que los métodos de acceso aseguran que un valor es siempre totalmente asignado u obtenido, incluso cuando los métodos de acceso son invocados de forma simultánea desde hilos diferentes.
Puedes utilizar Nonatomic para especificar que los métodos de acceso devuelvan un valor directamente, sin ninguna garantía sobre lo que suceda si ese mismo valor es accedido de forma simultánea desde diferentes hilos. Por esta razón, es más rápido para acceder a una propiedad nonatomic que una atomic.
Strong, weak y copy.
Strong indica que el valor debe ser retenido, normalmente se utiliza para cuando la propiedad guarda un objeto.
Assign se utiliza para valores escalares como int, float, BOOL.
Copy es para cuando una copia del objeto deba ser usada para la asignación.
Sintetizadores
Todas la propiedades deben ser sintetizadas en el archivo de implementación de una clase. Hoy en día el compilador puede auto-sintetizar las propiedades, sin embargo aún puedes hacerlo a mano.
@synthesize name, birthDate …
Si dejamos las propiedades auto-sintetizadas, se acceden a ellas con su mismo nombre y agregándole “_” al comienzo. Por ejemplo, la propiedad *name*, en el archivo .m se llama _name.
Asignando valores por defecto
Vamos a sobreescribir el constructor de la clase person para asignar unos valores por defecto. En el archivo .m escribimos el siguiente método.
- (id)init { self = [super init]; if (self) { // asignando a una variable por su nombre name = @"Oscar"; // asignando por propiedad con metodo de acceso [self setName:@"Oscar"]; [self setBusinessPerson:YES]; // asignando por propiedad sin metodo de acceso _name = @"Oscar"; _businessPerson = YES; // asignando por propiedad con sintaxys de punto self.name = @"Oscar"; self.businessPerson = YES; // Accediendo a una variable por su metodo personalizado if ([self isBusinessPerson]) { // Accediendo a una variable por su propiedad NSLog(@"%@ es una persona de negocios", self.name); NSLog(@"%@ es una persona de negocios", [self name]); NSLog(@"%@ es una persona de negocios", _name); // estos tres últimos hacen lo mismo. } } return self; }
Como puedes ver hay muchas maneras de hacer las cosas.
Protocolos
Las interfaces en Objective-C son llamadas protocolos.
En los protocolos podemos declarar métodos y propiedades (pero luego deben ser sintetizadas en el .m de la clase que implemente el protocolo, no se pueden auto-sintetizar).
Para crear un protocolo en nuestro proyecto vamos a File > New y seleccionamos “Cocoa Touch” -> “Objective-C protocolo”.
Y especificamos el nombre de nuestro protocolo. Yo lo llamé Entidad.
Ahora simplemente agregamos las propiedades o métodos que queramos.
@protocol Entity
Ahora hacemos que nuestra clase Person implemente estos métodos.
Person .h:
#import "Entity.h" #import Foundation.h> @interface Person : NSObjectEs importante recordar que hay que importar Entity.h
Person .m:
… @synthesize identifier; …Hay que sintetizar identifier porque viene de un protocolo.
Person .m:
… @synthesize identifier; …Implementamos el método generateIdentifier”:
Person .m:
… - (void)generateIdentifier { self.identifier = 123545; NSLog(@"Mi identificador es: %i", self.identifier); } …
Categorías
Las categorías son una manera de agregar métodos a una clase sin necesidad de heredar de ella.
Supongamos que queremos agregar un método a nuestra clase Person sin tocarla.
En Xcode vamos a File > New y seleccionamos “Cocoa Touch” -> “Objective-C class extension”.
Y especificamos el nombre de nuestra categoría y la clase que extiende. Yo la llamé Runner.
Agregar un método se hace exactamente igual que como hacemos con las clases. Escribimos la declaración en el .h y la implementación en el .m. Luego con solo importar la categoría todas las instancias de la clase Person, tendrán esta método.
Yo voy a incluir el método run a nuestra clase:
Person+Runner.h
#import "Person.h" @interface Person (Runner) - (void)run; @endPerson+Runner.m
#import "Person+Runner.h" @implementation Person (Runner) - (void)run { NSLog(@"Corriendo"); } @end
Actualizando main.m
Ahora vamos a main.m e incluiremos llamados los nuevos métodos.
main.m
#import "Person+Runner.h" // cambiamos Person.h por la categoria ya que esta ya la incluye #import Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { Person *oscar = [[Person alloc] init]; / ahora este utiliza el nuevo init que escribimos [oscar walk]; [oscar jumpHeight:107.3f]; [oscar runDistance:10.5f withSpeed:8.0f]; [Person age]; // Método del protocolo [oscar generateIdentifier]; // Método de la categoria [oscar run]; } return 0; }
Conclusión
En este capítulo hemos aprendido el lado que faltaba de la orientación a objetos de Objective-C. Hemos visto como crear ivars, propiedades, protocolos y categorías. Y con esto hemos cubierto todos los conceptos básicos necesarios para trabajar con objetos.
¡Hasta el próximo capítulo!.