Revista Informática

Objective-C desde Cero: Orientación a Objetos – parte 2

Publicado el 15 julio 2013 por Codehero @codeheroblog

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”.

Nuevo protocolo en Xcode

Y especificamos el nombre de nuestro protocolo. Yo lo llamé Entidad.

Nombre de nuevo protocolo en Xcode

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 : NSObject 

Es 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”.

Nueva categoría en Xcode

Y especificamos el nombre de nuestra categoría y la clase que extiende. Yo la llamé Runner.

Nombre de nueva categoría en Xcode

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;

@end

Person+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!.


Volver a la Portada de Logo Paperblog