Category: macOS

Articles about Apple’s Operating System for desktop and laptop computers: macOS.

Naming your partitions

I’ve been playing with different names for partitions on Mac OS X and I’ve found two interesting things:

Spaces in the name

Avoid spaces in the name. At all costs. Some make script are not properly written and don’t work when the partition where the file is located contains a space. Usually the output is something like: HD/Users/sumolari/script.c  not found. OS X uses as default name for system’s partition Macintosh HD. You can easily scape the space using: Macintosh\ HD when writing paths in shell, but some developers don’t worry about that and their scripts just don’t compile. I’ve found this with npm modules that use native code (for instance, ZMQ), as well as Ruby gems (Berkshelf was one of them).

Time Machine

Some months ago I added a solid state disk to my MacBook. I use the typical setup for SSD+HD: I installed OS X in my SSD and moved my user folder to the HD. I enabled Time Machine and I didn’t receive any complain from it, so I kept using it as before.

A few days ago I replaced my HD and when I reinstalled OS X I tried to restore my Time Machine backup. It couldn’t restore my user’s home folder because it was located on a different partition than the system. I was worried but as soon as I logged in my not-restored account (which I moved again to the HD) I run Time Machine and it allowed me to restore all the documents that where there.

Not as clean as a checkbox during OS X installation, but definitively more convenient than losing the files or having to backing them up in other disk before formatting the partition.

Target disk mode

I’ve an iMac and a MacBook. Both of them had only two partitions: SSD and HD. SSD had only OS X and some applications installed and HD had my user account. I started my MacBook in target disk mode and then booted the iMac using MacBook’s SSD partition as boot partition. OS X loaded my user account from iMac’s HD partition, so I had the home folder of my user in the iMac but the applications that were installed on my MacBook. Pretty weird. I finally renamed the partitions to be different on each computer.

Java + OS X: Put Menu Bar in the right place

Dear  programmers who develop Java applications for OS X, please, add this line to your application:

System.setProperty("apple.laf.useScreenMenuBar", "true");
Code language: Java (java)

It will place the menu bar where it is expected to be in an OS X application.

PostgreSQL in OS X with logo menu

Mac OS X, starting with Lion, has PostgreSQL built-in. For some projects being able to use this build-in PostgreSQL server is a very handy feature. We could start it through Terminal but there are easier approaches, for sure.

In fact, the easiest approach I’ve found so far is It’s just a menubar-application that starts PostgreSQL and shows you the port listened by PostgreSQL, a shortcut to psql command line, documentation and the ability to automatically start the server on system start up.

A direct access to PostgreSQL configuration would be nice, but even without it is very useful. And it’s free!

Guardar objetos en archivos con Objective-C

Ayer Carmen Parra preguntaba en un comentario cómo se pueden guardar los datos introducidos por el usuario en un archivo. Como el proceso es demasiado largo como para publicarlo en un comentario, he decidido escribir este pequeño tutorial.

NSArray, NSDictionary, NSString, NSNumber, NSData y otros

Resultado del código de ejemploHay dos métodos sencillos para almacenar un objeto en un archivo y poder recuperarlo cuando nos convenga. El primero no se puede aplicar a cualquier objeto, sino a objetos de algunas clases de Objective-C que responden a dos métodos llamados writeToFile:atomically: y writeToURL:atomically:. El uso de estos métodos es muy sencillo, basta con llamarlos en el momento en el que queramos que el objeto se guarde en un archivo.

A continuación teneís un ejemplo comentado. Básicamente creamos una matriz, le añadimos algo de contenido, la imprimimos, la guardamos en un archivo y nos deshacemos de ella. A continuación creamos otra matriz que lea el contenido del archivo y comprobamos que tiene el mismo contenido que la anterior. El resultado que obtendréis será similar al de la imagen de la derecha.

NSMutableArray *array = [[NSMutableArray alloc] init]; // Creamos una matriz [array addObject:@"¡Hola"]; // Añadimos un objeto [array addObject:@"mundo!"]; // Añadimos otro objeto NSLog(@"Matriz original: %@", array); // Imprimimos la matriz original [array writeToFile:@"filename" atomically:YES]; // Escribimos en el archivo filename el contenido de la matriz [array release]; // Liberamos memoria NSMutableArray *arrayFromFile = [[NSMutableArray alloc] initWithContentsOfFile:@"filename"]; // Esta matriz tiene el mismo contenido que tenía la matriz anterior NSLog(@"%@", arrayFromFile); // Imprimimos la matriz recuperada del archivo [arrayFromFile release]; // Liberamos memoria
Code language: Objective-C (objectivec)

Del mismo modo que se puede guardar en un archivo un objeto de la clase NSArray, también se puede usar con objetos NSDictionary, NSString, NSData y NSNumber, entre otros. Sin embargo no se puede usar con objetos de una clase creada por nosotros (en la mayoría de los casos).

Clases creadas por nosotros

Para guardar en un archivo un objeto de una clase creada por nosotros, tendremos que hacer que el objeto sigua el protocolo NSCoding. Tras esto, mediante las clases NSKeyedArchiver y NSKeyedUnarchiver podremos guardar en un archivo y leer de un archivo nuestro objeto. Este protocolo tiene dos métodos necesarios: initWithCoder: y encodeWithCoder:.

Vamos a suponer que hemos creado la clase Empleado, que tiene la siguiente interfaz:

@interface Empleado : NSObject @property (nonatomic, retain) NSString *nombre, *apellidos; @property NSUInteger edad; @property BOOL esEmpleadoDelMes; @end
Code language: Objective-C (objectivec)

Para poder almacenar este objeto en un archivo tendremos primero que hacer que siga el protocolo NSCoding. Modficaremos la interfaz y la dejaremos así:

#define kNombre @"Nombre" #define kApellidos @"Apellidos" #define kEdad @"Edad" #define kEsEmpleadoDelMes @"esEmpleadoDelMes" @interface Empleado : NSObject @property (nonatomic, retain) NSString *nombre, *apellidos; @property NSUInteger edad; @property BOOL esEmpleadoDelMes; @end
Code language: Objective-C (objectivec)

Básicamente hay dos cambios: hemos indicado que la clase sigue el protocolo NSCoding y hemos definido una serie de constantes que usaremos más adelante y que entenderéis en seguida su utilidad.

Ahora pasamos a la implementación. Esta es la implementación original de la clase Empleado, antes de seguir el protocolo NSCoding:

@implementation Empleado @synthesize nombre, apellidos; @synthesize edad; @synthesize esEmpleadoDelMes; - (void)dealloc { [nombre release]; [apellidos release]; [super dealloc]; } @end
Code language: Objective-C (objectivec)

Ahora vamos a añadir los dos métodos que necesitamos para seguir el protocolo NSCoding. Estos métodos nos permiten archivar nuestro objeto. El método encodeWithCoder: tiene por parámetro un objeto de la clase NSCoder. En este objeto archivaremos las propiedades del nuestro, asignándole a una serie de claves las propiedades del objeto.

Por ejemplo, si queremos hacer que la propiedad nombre se archive en la clave clave, el método tendría el siguiente aspecto:

- (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.nombre forKey:@"clave"]; }
Code language: Objective-C (objectivec)

No todas las propiedades se archivan de la misma forma. Los objeto se archivan a través del método encodeObject:forKey:, los números enteros se archivan con encodeInteger:forKey: y los valores booleanos con encodeBool:forKey:. Las claves son muy importantes, ya que el método initWithCoder: va a desarchivar el objeto usando las mismas claves para acceder a las propiedades, por eso hemos definido una serie de constantes que serán las claves que usaremos para archivar y desarchivar las propiedades.

Nota: Tenéis una lista de todos los métodos para archivar objetos, así como una explicación del proceso de archivar y desarchivar objetos en la documentación de Apple (en inglés).

Nuestro método encodeWithCoder: quedará así:

- (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.nombre forKey:kNombre]; [coder encodeObject:self.apellidos forKey:kApellidos]; [coder encodeInteger:self.edad forKey:kEdad]; [coder encodeBool:self.esEmpleadoDelMes forKey:kEsEmpleadoDelMes]; }
Code language: Objective-C (objectivec)

El método initWithCoder: es muy similar al anterior, sólo que su funcionamiento es el contrario: a partir de un objeto archivado se obtienen las propiedades y se crea un objeto de la clase Empleado (en este caso) con las propiedades que tenía el objeto al archivarse. En este caso enviaremos el mensaje decodeObjectForKey:@”clave” a coder y asignaremos el valor devuelto a la propiedad de nuestro objeto. De nuevo, se usan métodos diferentes para decodificar número enteros, valores booleanos y otros tipos de variables. Nuestro método initWithCoder: quedará así:

- (id)initWithCoder:(NSCoder *)coder { if ((self = [super init])) { self.nombre = [coder decodeObjectForKey:kNombre]; self.apellidos = [coder decodeObjectForKey:kApellidos]; self.edad = [coder decodeIntForKey:kEdad]; self.esEmpleadoDelMes = [coder decodeBoolForKey:kEsEmpleadoDelMes]; } return self; }
Code language: Objective-C (objectivec)

Resultado del ejemploAhora pasemos a crear un objeto Empleado, a guardarlo en un archivo y a comprobar que funciona correctamente. Primero incluímos la interfaz de la clase Empleado en el archivo en el que vamos a crear el objeto, en mi caso, y al tratarse de una “Command Line Tool“, lo incluiré en el archivo main.m. El siguiente código comentado crea un objeto de la clase Empleado, le asigna algunos valores sus propiedades, lo archiva y después crea un nuevo objeto con el contenido del archivo en el que se ha guardado antes el objeto:

Empleado *trabajador = [[Empleado alloc] init]; // Creamos el objeto trabajador.nombre = @"Juan"; // Le damos un nombre trabajador.apellidos = @"García López"; // Unos apellidos trabajador.edad = 26; // Una edad trabajador.esEmpleadoDelMes = YES; // Y lo hacemos empleado del mes NSLog(@"%@ %@ tiene %lu años", trabajador.nombre, trabajador.apellidos, trabajador.edad); // Imprimimos sus datos [NSKeyedArchiver archiveRootObject:trabajador toFile:@"empleado"]; // Lo guardamos en el archivo empleado [trabajador release]; // Liberamos memoria Empleado *trabajadorRecuperado = [NSKeyedUnarchiver unarchiveObjectWithFile:@"empleado"]; // Creamos un objeto a partir del archivo empleado NSLog(@"%@ %@ tiene %lu años", trabajadorRecuperado.nombre, trabajadorRecuperado.apellidos, trabajadorRecuperado.edad); // Imprimimos sus datos
Code language: Objective-C (objectivec)

Así se puede guardar un objeto de cualquier clase en un archivo, de forma que puede ser recuperado en cualquier momento. Los objetos más complejos pueden necesitar métodos más complejos a la hora de archivarlos. Del mismo modo no se tienen por qué archivar todas las propiedades si no se quiere.

Por último dejo a vuestra disposición los archivos del proyecto que he creado mientras escribía el tutorial, por si os atascáis y necesitáis un empujoncito.

Borra archivos de un pendrive en Mac OS X sin vaciar la papelara

Editado: En la primera versión de esta pequeña aplicación había un error que provocaba que en unidades con nombres que incluyesen espacios o caracteres especiales no se borrasen los archivos de forma definitiva. Se ha resuelto y he actualizado el artículo añadiendo el enlace para descargar la última versión en lugar de la que tenía el error. ¡Gracias a Jorosa por el aviso!

Archivos "borrados" del pendrive
Hay cerca de 800MB de archivos "borrados" en este pendrive

Hay veces que uno se sorprende al ver ciertos comportamientos de Mac OS X. Uno de los que menos me gustan es la forma de borrar archivos de un dispositivo extraíble (por ejemplo, un pendrive). En lugar de borrar el archivo, como sería de esperar, o moverlo a la papelera del Mac, lo almacena en una carpeta oculta dentro del propio pendrive que usa a modo de papelera. Si nos fijamos en la papelera de nuestro Mac, veremos que los archivos que hemos borrado del pendrive aparecen en ella, aunque no estén almacenados en el Mac.

El problema radica en que además de no borrar realmente los archivos y dejarlos en el pendrive (ocupando parte de su preciada capacidad), no hay forma de borrar los archivos individualmente de la papelera. Si usamos la combinación Función + Retroceso lo que hacemos es devolver el archivo eliminado a su carpeta de origen en lugar de borrarlo. Estamos pues obligados a vaciar la papelera y borrar definitivamente otros archivos que tal vez no queramos perder todavía.

Así que, algo mosqueado porque esto no haya cambiado ni en Snow Leopard ni en Lion (ni tiene pinta de que vaya a cambiar), he creado una simple aplicación en Automator para “limpiar” los pendrives de archivos supuestamente eliminados.

Cómo funciona

La carpeta .Trashes al descubierto
Esto lo aclara todo

Antes de dar los enlaces para descargar la aplicación, veamos cómo funciona. Como ya he dicho, Mac OS X mueve los archivos “borrados” a una carpeta oculta que funciona a modo de papelera. Esta carpeta se llama .Trashes (como de costumbre en OS X, al llevar un punto al principio del nombre, la carpeta es oculta). Si borramos la carpeta .Trashes, borramos todos los archivos que queríamos eliminar pero, ¿cómo la borramos sin pasar por la papelera de nuevo?

Aquí es donde entra en juego el potencial de un sistema basado en Unix. Gracias al comando rm (remove) podemos eliminar archivos o carpetas sin necesidad de pasar por la papelera. Combinando esto con un par de parámetros, concretamente -R (borrar de forma recursiva) -f (sin pedir confirmación) y -d (directorios y archivos) conseguimos vaciar el pendrive de archivos que queríamos borrar.

Obviamente tener que escribir todo esto en la terminal cada vez que queremos limpiar un pendrive de archivos “borrados” no es muy cómodo. Para hacer más cómoda la tarea recurriremos a Automator, ese pobre incomprendido que si bien es capaz de hacer maravillas, la mayoría de los usuarios desconoce incluso su existencia.

Creamos una nueva aplicación en AutomatorEl flujo resultantePara crear la aplicación abrimos Automator y elegimos crear una aplicación. A continuación arrastramos las acciones para formar el flujo que podéis ver en la imagen de la derecha. Básicamente el flujo es el siguiente:

  1. Solicitamos una carpeta. El usuario deberá seleccionar un dispositivo extraíble.
  2. Establecemos el valor de una variable con la ruta de la carpeta seleccionada.
  3. Leemos el valor de esta variable y se lo pasamos a un script Shell como parámetro (este paso y el anterior supongo que se pueden omitir y usar el resultado del paso 1 como argumentos para el paso 4, pero por pereza no lo he comprobado – si alguien se anima que lo comente en los comentarios).
  4. En el script Shell usamos el comando cd (change dir) para ir a la ruta del pendrive.
  5. A continuación, en el mismo Shell y cambiada la ruta a la que nos interesa, borramos la carpeta .Trashes con los parámetros que he expuesto antes.


Seguidos los pasos y entendido el funcionamiento, aquí tenéis a vuestra disposición la aplicación.

Introducción a Objective-C: El paradigma de la POO

Siguiendo con los tutoriales de Objective-C, voy a explicar el paradigma de la Programación Orientada a Objetos. En la POO de Objective-C hay 5 conceptos clave:

  • Objeto: Los objetos son el pilar fundamental en la POO. Los objetos contienen variables, responden a métodos, tienen una dirección de memoria en la que están almacenados, pertenecen a una clase y pueden implementar protocolos.
  • Clases: Un objeto pertecen a una clase, y sólo a una. Por el contrario varios objetos pueden pertenecer a la misma clase. En otros lenguajes de programación los objetos pueden heredar de varias clases, pero en Objective-C no. Cuando un objeto pertecene a una clase, el objeto contiene todas las variables de la clase, y responde a todos los métodos de instancia a los que responde la clase (veremos qué es esto más adelante). A su vez, una clase puede heredar de otra clase (de hecho lo más común es que las clases que creemos hereden de NSObject, al menos a nuestro nivel), cuando esto sucede, la nueva clase responde a todos los métodos a los que respondía la clase “padre” y los objetos de esta clase también tienen las variables de instancia de la clase “padre”.
  • Variables de instancia: Los objetos, a pesar de ser por sí mismos variables, pueden contener a otras variables, que pueden ser variables simples como las de C (por ejemplo, números enteros o de coma flotantes) u objetos de cualquier clase. Las variables de instancia son diferentes en cada objeto, por lo que si tenemos dos objetos de la clase rueda y accedemos a la variable presiónDeLaRueda, cada uno devolverá un valor diferente de presión.
  • Métodos de instancia: Los métodos de instancia son las funciones que puede ejecutar el objeto de una clase determinada. Cada clase define unos métodos de instancia diferente. Por ejemplo, la clase coche podría tener un método que fuese encenderMotor, al que se accedería desde un objeto determinado.
  • Métodos de clase: Los métodos de clase son funciones a las que sólo se puede acceder desde la propia clase. Un método de clase típico es alloc, al que sólo se puede acceder desde una case, nunca desde un objeto.

Puede que no os hayan quedado claros del todo los conceptos con esta introducción, los explicaré mejor un poco más adelante.

Creando una nueva clase
Creando una nueva clase
Creando una nueva clase - Paso 2
Creando una nueva clase - Paso 2

En Objective-C las clases se definen mediante dos archivos: NOMBRECLASE.h y NOMBRECLASE.m. Los archivos .h contienen la interfaz de la clase y los archivos .m su implementación. La interfaz de la clase es una especie de índice: indica todas las varibles de instancia de la clase y todos los métodos a los que responde. La implementación es donde se desarrolla el código de las funciones. No es necesario dividir las clases en dos archivos, pero es altamente recomendable para hacer más claro el código.

Para añadir un nuevo archivo a nuestro proyecto basta con ir al menú File » New file… o pulsar Comando + N. En la ventana que nos aparecerá seleccionaremos Mac OS X » Cocoa Class » Objective-C Class (Subclass of NSObject). En  File Name introduciremos el nombre de nuestra nueva clase, en este caso Coche, acabado en .m. También marcaremos la casilla Also create “Coche.h”, para mantener más limpio el código.

En el archivo Coche.h encontraremos el siguiente código:

#import <Cocoa/Cocoa.h> @interface Coche : NSObject { } @end
Code language: Objective-C (objectivec)

La primera línea ya la vimos en la primera parte de los tutoriales, e importa el Framework Cocoa. En la siguiente línea comienza la interfaz de la clase. Veamos su sintaxis:

  • @interface: Inicia la interfaz
  • Coche: El nombre de la clase que estamos definiendo
  • : : Separado entre el nombre de la clase y la clase de la que hereda.
  • NSObject: La clase de la que hereda nuestra clase.
  • { : Abre el bloque donde se definen las variables de instancia.
  • } : Cierra este bloque.
  • @end: Indica el fin de la implementación.

Como podéis ver, las clases pueden ser “hijas” de otras clases, y es algo realmente común. De hecho es muy conveniente que nuestras clases sean hijas de al menos NSObject. La clase NSObject es la clase básica e incluye las funciones esenciales de gestión de memoria (la veremos más tarde), entre otras cosas.

Si nuestra clase es hija de una clase que es hija de NSObject, esta nueva clase también tendrá acceso a los métodos de la clase NSObject.

Ahora bien, imaginemos que queremos que queremos que nuestra clase tenga alguna variable de instancia, como por ejemplo, el color del vehículo y el número de puertas. Definiremos dos variables: color y numPuertas. La primera, color, será un objeto de clase NSString (cadena de texto). La segunda, numPuertas, será una variable de tipo NSUInteger (a nuestro nivel, equivalente a un unsigned int de C, o en palabras llanas, un número entero sin signo).

#import <Cocoa/Cocoa.h> @interface Coche : NSObject { NSString    *color; NSUInteger    numPuertas; } @end
Code language: Objective-C (objectivec)

Fijáos en la sintaxis para definir varibles de instancia:

Code language: Objective-C (objectivec)

Al defnir un objeto siempre antepondremos un asterisco (“*”) al nombre del objeto.

Y ahora, ¿cómo obtenemos el valor de estas variables? Pues bien, tendremos que crear un método de instancia para asignar este valor. Los métodos se definen entre el corchete de cierre (“}”) y el @end. Creemos dos métodos de instancia: getColor y getNumPuertas. Creemos otro más llamado imprimirDetalles que muestre en la consola los valores devueltos por getColor y getNumPuertas.

#import <Cocoa/Cocoa.h> @interface Coche : NSObject { NSString    *color; NSUInteger    numPuertas; } - (NSString *)getColor; - (NSUInteger)getNumPuertas; - (void)imprimirDetalles; @end
Code language: Objective-C (objectivec)

Fijáos de nuevo en la sintaxis:

Code language: Objective-C (objectivec)

Como antes, cuando un método devuelve un objeto, se añade un asterisco al nombre de la clase del objeto que se devolverá. Cuando no es un objeto, no es necesario añadir el asterisco. Cuando una función no devuelve nada, se utiliza el valor void para indicárselo al compilador.

El guión (“-“) que precede a los métodos sirve para indicar que se trata de métodos de instancia. Si se tratase de métodos de clase (que veremos más adelante), estarían precedidos por un sigo +.

Pero aún nos faltan dos métodos para establecer el valor de las dos variables de instancias. Para ello definiremos dos métodos llamados setColor y setNumPuertas que acepten un parámetro que será el nuevo valor.

#import <Cocoa/Cocoa.h> @interface Coche : NSObject { NSString    *color; NSUInteger    numPuertas; } - (NSString *)getColor; - (NSUInteger)getNumPuertas; - (void)imprimirDetalles; - (void)setColor: (NSString *)nuevoColor; - (void)setNumPuertas: (NSUInteger)nuevoNumPuertas; @end
Code language: Objective-C (objectivec)

Analicemos la sintaxis de estas funciones. Como podéis ver, ninguna de las dos devuelve ningún valor, luego en el primer paréntesis escribiremos void. Los nombres están claros, pero al final de estos hay dos puntos (“:”). Esto indica que a continuación se encuentra un parámetro. Los parámetros tienen dos partes: la clase del objeto que es el parámetro o el tipo de la variable que es el parámetro y el nombre del mismo. La sintaxis es igual que la del tipo de valor que devuelve la función: El tipo de variable o la clase de objeto entre paréntesis (si es un objeto el parámetro se añade un asterisco) y a continuación el nombre del mismo. Las funciones pueden admitir varios parámetros, y pueden haber funciones con el mismo nombre pero diferentes parámetros. A continuación tenéis un ejemplo.

- (void)miFuncion; - (void)miFuncionConUnParametro: (NSUInteger)miParametro; - (NSString *)miFuncionConUnParametro: (NSUInteger)miParametro queAdemasDevuelveUnaCadenaDeTextoYRequiereOtroParametro: (NSUInteger)elSegundo;
Code language: Objective-C (objectivec)

Esto presenta un problema: ¿Cómo se llaman las funciones, si puede haber dos iguales hasta el primer parámetro? Pues bien, las funciones anteriores se llamarían así:

miFuncion miFuncionConUnParametro: miFuncionConUnParametro:queAdemasDevuelveUnaCadenaDeTextoYRequiereOtroParametro:
Code language: Objective-C (objectivec)

De este modo no son iguales los nombres a pesar de ser bastante parecidos (al menos en principio).

Con esto hemos creado la interfaz de nuestra clase: el índice de variables y métodos a los que responde. Sin embargo aún tenemos que escribir el código de estas funciones, así que vayamos al archivo Coche.m y comencemos a implementarlas. En el archivo .m veremos el siguiente código:

#import "Coche.h" @implementation Coche @end
Code language: Objective-C (objectivec)

Os suena, ¿verdad? Como en el caso anterior, vemos como se importa un archivo, en este caso la interfaz de nuestra clase. También vemos como se inicia la implementación con un código similar a la interfaz. Como podéis suponer, entre @implementation Coche y @end es donde escribiremos nuestras funciones.

Antes de implementar nuestras funciones, añadid el siguiente código después de @implementation Coche. Se trata de un método para la gestión de memoria que veremos en otro tutorial. Así de forma rápida, el método dealloc se llama cuando un objeto va a ser eliminado de la memoria. Los objetos tienen un contador interno que se aumenta con el método retain y se disminuye con el método release. Cuando el contador llega a 0, se llama automáticamente al método dealloc y se elimina el objeto de la memoria. De momento basta con que lo copiéis sin preguntar, ya lo explicaré más adelante.

- (void)dealloc { [color release]; [super dealloc]; }
Code language: Objective-C (objectivec)

Aprovechando este código os explicaré cómo se sobreescriben funciones ya existentes en la clase padre. Como he comentado, una clase que hereda de otra recibe las funciones de la clase padre, pero ¿qué sucede si queremos que en la clase hija la función heredada sea diferente? Pues nada, escribimos la nueva función como cualquier otra. La sintaxis es sencilla:

Code language: Objective-C (objectivec)

¿Qué ocurre si queremos que además de ejecutarse nuestro código se ejecute también el método (por si no os habéis dado cuenta, utilizo método y función para referirme a lo mismo, tal vez no es lo más correcto, pero estoy más acostumbrado a hablar de función y no de método, así que me sale sin pensar) de la clase padre que estamos sobreescribiendo? Pues añadimos [super NOMBRE_DE_LA_FUNCION]; en el lugar en el que queramos que se ejecute.

- (TIPO_DE_VALOR_QUE_SE_DEVUELVE)NOMBRE_DE_LA_FUNCION { // Podemos ejecutar el método de la clase padre antes que nuestro código [super NOMBRE_DE_LA_FUNCION]; // Código de la función // O después [super NOMBRE_DE_LA_FUNCION]; }
Code language: Objective-C (objectivec)

Veamos ahora cómo quedaría la implementación de nuestra clase tras añadir todos los métodos que hemos definido en la interfaz:

#import "Coche.h" @implementation Coche - (void)dealloc { [color release]; [super dealloc]; } - (NSString *)getColor { } - (NSUInteger)getNumPuertas { } - (void)imprimirDetalles { } - (void)setColor: (NSString *)colorNuevo { } - (void)setNumPuertas: (NSUInteger)nuevoNumeroDePuertas { } @end
Code language: Objective-C (objectivec)

Fijáos en que he cambiado el nombre de las variables de los argumentos de las dos últimas funciones. Las variables de los argumentos no tienen por qué tener el mismo nombre en la interfaz y en la implementación, pero tampoco pasa nada porque tengan el mismo nombre.

La función getColor y getNumPuertas tan sólo devuelven el valor de las variables correspondientes, así que añadiremos return VARIABLE; para que el método devuelva el valor de la variable. return no requiere asterisco si se devuelven objetos, se usa exactamente igual con objetos que con variables sencillas.

En la función imprimirDetalles utilizaremos la función NSLog para imprimir en la consola los valores de las variables. La función NSLog admite varios parámetros. El primero siempre es una cadena de texto (@””), pero esta cadena admite combinaciones de caracteres específicas que indican que se deben reemplazar por otro valor. Así un %d se reemplazará por un número entero, un %f por un número decimal y un %@ por otra cadena de texto (se puede usar tranquilamente un objeto también, lo veremos más adelante). Si usamos estas combinaciones, tras la cadena de caracteres, y separadas por comas (“,”) debemos introducir las variables o los objetos que reemplazarán las combinaciones. A continuación tenéis las tres funciones que hemos explicado:

- (NSString *)getColor { return color; } - (NSUInteger)getNumPuertas { return numPuertas; } - (void)imprimirDetalles { NSLog(@"Este coche es de color %@ y tiene %d puertas", [self getColor], [self getNumPuertas]); }
Code language: Objective-C (objectivec)

Fijáos en la última función, concretamente en [self getColor] y [self getNumPuertas]. Esta es la sintaxis para enviar mensajes a los objetos. Enviar un mensaje a un objeto es, básicamente, pedirle a un objeto que ejecute el método con ese nombre. Así, cuando escribimos [self funcion] le pedimos al objeto self que ejecute el método funcion. El objeto self es un objeto especial y hace referencia al objeto actual, de modo que lo que hacemos en esta última función es pedirle al objeto de clase Coche que ejecuta el método imprimirDetalles que ejecute el método getColor y getNumPuertas.

Las funciones setColor y setNumPuertas son bastante sencillas. Simplemente asignaremos (mediante “=”) el valor que se pasa como parámetro (o argumento) a la variable correspondiente. Aquí los tenéis completos:

- (void)setColor: (NSString *)colorNuevo { color = colorNuevo; } - (void)setNumPuertas: (NSUInteger)nuevoNumeroDePuertas { numPuertas = nuevoNumeroDePuertas; }
Code language: Objective-C (objectivec)

Y este es el aspecto final de la implementación de nuestra clase Coche:

#import "Coche.h" @implementation Coche - (void)dealloc { [color release]; [super dealloc]; } - (NSString *)getColor { return color; } - (NSUInteger)getNumPuertas { return numPuertas; } - (void)imprimirDetalles { NSLog(@"Este coche es de color %@ y tiene %d puertas", [self getColor], [self getNumPuertas]); } - (void)setColor: (NSString *)colorNuevo { color = colorNuevo; } - (void)setNumPuertas: (NSUInteger)nuevoNumeroDePuertas { numPuertas = nuevoNumeroDePuertas; } @end
Code language: Objective-C (objectivec)

Ahora modificaremos nuestra aplicación para que cree un objeto de clase coche, establezca su color en azul y su número de puertas en 5 y que a continuación imprima en la consola la información sobre el mismo.

Vamos al archivo NOMBREPROYECTO.m, que debería tener el siguiente código:

#import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert code here... NSLog(@"Hello, World!"); [pool drain]; return 0; }
Code language: Objective-C (objectivec)

E importamos el archivo Coche.h justo después de importarse Foundation:

#import "Coche.h"
Code language: Objective-C (objectivec)

A continuación añadimos, entre NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; y [pool drain];:

Coche *miCoche = [[Coche alloc] init]; [miCoche setColor:@"Azul"]; [miCoche setNumPuertas:5]; [miCoche imprimirDetalles]; [miCoche release];
Code language: Objective-C (objectivec)

Vayamos línea a línea:

Coche *miCoche = [[Coche alloc] init];
Code language: Objective-C (objectivec)

En esta línea creamos el objeto miCoche, de clase Coche. Fijáos como se le envía a  la clase Coche el mensaje alloc y al objeto resultante de ese mensaje se le envía el mensaje init. Los mensajes se pueden encadenar tanto como se quiera, aunque esto hace más complicado de leer el código, así que no es muy recomendable.

[miCoche setColor:@"Azul"]; [miCoche setNumPuertas:5];
Code language: Objective-C (objectivec)

En estas dos líneas se envía el método setColor: y setNumPuertas: al objeto miCoche, con el parámetro @”Azul” como nuevo color y 5 como número de puertas.

[miCoche imprimirDetalles];
Code language: Objective-C (objectivec)

En esta línea se le manda al objeto el mensaje imprimirDetalles, lo que hará que muestre por terminal los valores de las variables.

[miCoche release];
Code language: Objective-C (objectivec)

Por último esta línea elimina el objeto. En realidad es algo más complejo, y lo explicaré más adelante en otro tutorial. De momento con saber que se elimina el objeto nos vale.

El código resultante de MIPROYECTO.m será:

#import <Foundation/Foundation.h> #import "Coche.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert code here... NSLog(@"Hello, World!"); Coche *miCoche = [[Coche alloc] init]; [miCoche setColor:@"Azul"]; [miCoche setNumPuertas:5]; [miCoche imprimirDetalles]; [miCoche release]; [pool drain]; return 0; }
Code language: Objective-C (objectivec)

Y si compilamos y ejecutamos la aplicación veremos el siguiente resultado:

Resultado de la segunda aplicación

Esto no está mal, pero, ¿para qué llamar a dos métodos para establecer las variables y otro más para iniciar el objeto (init)? ¿No podríamos juntar esos tres métodos en uno? Sí, podemos. Vayamos al archivo Coche.h y añadamos el siguiente método:

- (Coche *)initWithColor: (NSString *)nuevoColor andNumPuertas: (NSUInteger)nuevoNumPuertas;
Code language: Objective-C (objectivec)

Quedando así la interfaz:

#import <Cocoa/Cocoa.h> @interface Coche : NSObject { NSString    *color; NSUInteger    numPuertas; } - (Coche *)initWithColor: (NSString *)nuevoColor andNumPuertas: (NSUInteger)nuevoNumPuertas; - (NSString *)getColor; - (NSUInteger)getNumPuertas; - (void)imprimirDetalles; - (void)setColor: (NSString *)nuevoColor; - (void)setNumPuertas: (NSUInteger)nuevoNumPuertas; @end
Code language: Objective-C (objectivec)

En la implementación añadiremos lo siguiente:

- (Coche *)initWithColor: (NSString *)nuevoColor andNumPuertas: (NSUInteger)nuevoNumPuertas { if (self = [super init]) { [self setColor:nuevoColor]; [self setNumPuertas:nuevoNumPuertas]; } return self; }
Code language: Objective-C (objectivec)

Analicemos este código. La primera línea de la función, if (self = [super init]) sirve para comprobar que el objeto se ha creado correctamente. Es posible (aunque raro) que por algún motivo el objeto no pueda crearse correctamente (por ejemplo, por falta de memoria), así que en caso de que no se cree correctamente, no estableceremos el valor de ninguna variable (pues de otra manera haríamos fallar la aplicación). Dentro de la condición se envía los mensajes que ya conocemos al objeto y finalmente se devuelve el objeto self. Como podéis ver, esta función devuelve un objeto de clase Coche.

El código final de nuestra implementación será:

#import "Coche.h" @implementation Coche - (Coche *)initWithColor: (NSString *)nuevoColor andNumPuertas: (NSUInteger)nuevoNumPuertas { if (self = [super init]) { [self setColor:nuevoColor]; [self setNumPuertas:nuevoNumPuertas]; } return self; } - (void)dealloc { [color release]; [super dealloc]; } - (NSString *)getColor { return color; } - (NSUInteger)getNumPuertas { return numPuertas; } - (void)imprimirDetalles { NSLog(@"Este coche es de color %@ y tiene %d puertas", [self getColor], [self getNumPuertas]); } - (void)setColor: (NSString *)colorNuevo { color = colorNuevo; } - (void)setNumPuertas: (NSUInteger)nuevoNumeroDePuertas { numPuertas = nuevoNumeroDePuertas; } @end
Code language: Objective-C (objectivec)

¿Cómo aplicamos estos cambios a nuestra aplicación? Vayamos al archivo NOMBREPROYECTO.m y reemplacemos el siguiente código:

Coche *miCoche = [[Coche alloc] init]; [miCoche setColor:@"Azul"]; [miCoche setNumPuertas:5];
Code language: Objective-C (objectivec)

Por este otro:

Coche *miCoche = [[Coche alloc] initWithColor:@"Azul" andNumPuertas:5];
Code language: Objective-C (objectivec)

Como veis, hemos convertido tres líneas de código en sólo una, y el resultado es idéntico.

Esto tan sólo es una breve introducción al paradigma de la programación orientada a objetos. Espero que os haya aclarado un poco los conceptos. En los próximos tutoriales iremos viendo más características de la POO en Objective-C.

Introducción a Objective-C: Nuestra primera aplicación

¿Qué es Objective-C? ¿Qué es Cocoa? ¿Qué es la Programacion Orientada a Objetos (POO)? ¿Y la arquitectura de Modelo Vista Controlador (MVC)? Dar los primeros pasos en la programación para Mac OS X, iPhone y iPad puede ser algo complicado si no sabes dónde buscar la información (y aún más difícil si no sabes inglés, imprescindible en el mundo de la informática). Hace año y medio, cuando yo comencé a aprender a programar para el iPhone (no tengáis prisa, sigo haciéndolo y me queda para mucho rato), el primer problema que tuve fue encontrar fuentes de información para dar los primeros pasos, así que he decido escribir esta serie de tutoriales para facilitar la tarea a aquellos que, como yo, quieran aprender a programar para el iPhone (extensible a Mac OS X y iPad). Tampoco esperéis ser unos maestros de Objective-C después de haber leído esta serie de tutoriales (que iré publicando cuando vaya teniendo tiempo para redactarlos), pues mis conocimientos son fruto del autodidactismo y seguramente no utilice los términos adecuados ni profundice del todo en los conceptos que trate. Sin embargo espero que os sirva para dar el primer paso y poder continuar después con textos mejor redactados, más exactos y más profundos. Read more →

Añade las llaves en una nueva línea en Xcode

Xcode: Antes y después
Antes (izquierda) y después (derecha)

Cuando se lleva algún tiempo programando se acaban cogiendo manías y estilos a la hora de escribir el código, una de las más comunes es escribir las llaves ({ y }) en la línea siguiente, en lugar de en la línea en la que acaba la sentencia, manía que además suele considerarse como buena práctica.

// Con las llaves en la misma línea - (void)funcionDePrueba { if (condicion) { [self otraFuncion]; } } // Con las llaves en la línea siguiente - (void)funcionDePrueba { if (condicion) { [self otraFuncion]; } }
Code language: Objective-C (objectivec)

La cuestión es que ahora que estoy programando en Objective-C y utilizo Xcode, se me hace muy molesto que el autocompletado de código me añada las llaves en la misma línea que la sentencia, en lugar de la siguiente (cuando programo en PHP, el editor que uso no autocompleta el código). Sin embargo hay una forma sencilla de hacer que añada las llaves en la línea siguiente: tan sólo tenemos que escribir el siguiente código en la terminal y reiniciar Xcode.

defaults write XCCodeSenseFormattingOptions '{ "BlockSeparator" = "n" ; }'

Esto sólo afecta al autocompletado de código, no a las plantillas que vienen con Xcode.

Dropbox, sincroniza carpetas entre tus equipos

DropboxUna de las cosas que se hacen molestas de tener varios equipos es que acabas diseminando los archivos que más usas entre ellos, de modo que en el sobremesa acabas teniendo archivos que no están en el portátil, y en el portátil acabas teniendo archivos que no están en el sobremesa. Para poder acceder a estos archivos acabas teniendo que crear una carpeta compartida en ambos equipos para ir actualizando los archivos que más sueles usar, utilizas un pendrive para trasportarlos, o acabas enviándote por emails los archivos cada vez que los modificas, sin embargo estas no son las opciones más cómodas.

Dropbox es un gran alternativa para este problema. Se trata de una aplicación que crea una carpeta en nuestro equipo que se sincroniza con una carpeta en un servidor remoto. Cada vez que añadimos algo en nuestra carpeta, se añade en la carpeta del servidor remoto, de modo que podemos acceder a los archivos de dicha carpeta desde cualquier ordenador conectado a Internet, a través de la web de Dropbox. También podemos instalar la aplicación en todos nuestros equipos, y al modificar un archivo de la carpeta de Dropbox en un equipo, se modifica en todos los demás equipos que tengan la aplicación instalada, sin necesidad de estar todos encendidos a la vez, ya que se sincronizan con el servidor remoto.

La aplicación es gratuita y está disponible tanto para Windows como para Mac OS X y Linux, además de móviles con Android, iPhone, iPad y próximamente Blackberry.  Ofrece un espacio de 2GB de forma totalmente gratuita, aunque por 9.99$ al mes podemos ampliar este espacio a 50GB y por 19.99$ disponemos de 100GB. También podemos ampliar nuestro espacio disponible de forma gratuita invitando a nuestros amigos a Dropbox de modo que por cada amigo que se registre en Dropbox ampliaremos en 250MB la capacidad de nuestra cuenta, hasta llegar al límite de 8GB.

Convierte tus PDF en ePub

Parece que Apple está interesada en que sus dispositivos con iOS (iPhone, iPod y iPad) tengan acceso a iBooks, una aplicación que nos permite gestionar y leer libros. Desde la versión 1.1 podemos leer PDF desde la misma aplicación, sin embargo hay ciertas opciones que no podemos configurar al leer PDF y sí al leer ePub, como el tamaño de la letra, lo que hace que leer, por ejemplo, el manual de usuario de iOS 4 sea realmente incómodo al tener que hacer zoom cada dos por tres.

Afortunadamente tenemos una alternativa: podemos convertir nuestros PDF en ePub. Hay muchas aplicaciones para hacer esto, tanto online como offline, pero en este artículo sólo me centraré en una aplicación. Calibre (así es como se llama la utilidad que nos permitirá convertir nuestros PDF en ePub) es totalmente gratutia y está disponible tanto para Windows como para Linux y Mac OS X. Read more →