Otra manera de programar en Cocoa. Ruby y Python.

abril 28, 2010

Cuando pensamos en desarrollar aplicaciones para Mac, siempre nos viene a la mente Cocoa, y junto a este Framework aparece el lenguaje mas maquero, Objetive-C. Sin embargo, es posible hacer aplicaciones completas usando otros lenguajes, como puede ser Java, C o C++ y los que, desde mi punto de vista, son mas interesantes, estoy hablando de los lenguajes de Script de “moda” Ruby y Python.

Gracias a RubyCocoa y PyObjc, podemos invocar a métodos de clases desarrolladas en ObjetiveC, lo que quiere decir que vamos a ser capaces de utilizar las librerias de Cocoa, desde nuestro lenguaje de script favorito. Además, como ambos vienen instalados por defecto en nuestros OSX, no tendremos que instalar nada para empezar a desarrollar con ellos.

Aparte de la posibilidad de crear aplicaciones Cocoa únicamente usando estos lenguajes de script, se nos abre otra posibilidad bastante interesante. Gracias a la inclusión en OSX del Scripting Bridge y combinándolo con la posibilidad que comentabamos de usar Python o Ruby para usar librerías hechas en Objetive-C, podremos comunicarnos con las aplicaciones Cocoa usando estos lenguajes.

Antiguamente, si hablamos de lenguajes de script, esta posibilidad de comunicación entre aplicaciones estaba limitada al propio AppleScript.

Particularmente no tengo nada en contra de AppleScript, pero siempre me pareció un lenguaje un tanto complicado de aprender, sin embargo, Python o Ruby me parecen lenguajes bastante prácticos, por lo que el hecho de poder usarlos como medio de comunicación con otras aplicaciones Cocoa, me parece una idea tremenda 🙂

A partir de OSX 10.5 se introdujo en el sistema operativo lo que se conoce como Scripting Bridge. Este sistema permite comunicarse con los objetos en ejecución usando mensajes de Objetive-C. Anteriormente la única manera de comunicarse con las aplicaciones era usando comandos AppleScript, por eso, este era el único lenguaje con el que podíamos, por ejemplo, recuperar las canciones de nuestra lista de iTunes.

Como seguramente habreís podido deducir, gracias también al Scripting Bridge, podemos comunicarnos con dichas aplicaciones usando el omnipresente Objetive-C

La inclusión del Scripting bridge, de la misma manera, es cruicial para la parte “servidora”, es decir, el iTunes o el Safari, ya que ha simplificado en gran medida la forma de añadir soporte de Script a una aplicación, pese a que esta tarea sigue siendo no trivial.

Como siempre, la mejor forma de aprender algo es viendo algún ejemplo. Así que vamos a utilizar a Python para listar todas las canciones que tenemos en nuestra librería de iTunes.

El código para realizar esto es bastante sencillo como podemos ver:

import ScriptingBridge

iTunes = ScriptingBridge.SBApplication.applicationWithBundleIdentifier_("com.apple.iTunes")

for source in iTunes.sources():
	print source.name()
	for playlist in source.playlists():
		print "\\---", playlist.name()
		for track in playlist.tracks():
			print "\t\\---", track.name()

Si lo ejecutamos, veremos como se lanza “silenciosamente” iTunes, y momentos después se nos muestra la lista de canciones.

Es importante apreciar que con sólo 6 lineas, estamos salvando muchisimas dificultades que existían en el pasado, y sobre todo, estamos abriendo una posibilidad tremenda para crear nuevas aplicaciones.

Ahora bien, os podeis preguntar, ¿Cómo se que métodos tiene un objeto SBApplication determinado?, ya que por mucho que sea sencillo, si no tenemos documentación al respecto no podemos hacer nada.

Para averiguar que nos ofrece una aplicación existen varios métodos, el mas “visual” es usando la opción de “Abrir Diccionario” del “AppleScript Editor”. Como vemos en la captura.

El otro método “menos amigable” es usar los comandos sdef y sdp, que se encargan de “extraer” la definiciónes de script que posee una aplicación; como podemos ver en la captura de la terminal.

Estos comandos nos sacaran un fichero .h que podemos utilizar para importarlo en nuestro proyecto de Objetive-C, y que desde el punto de vista de nuestros lenguajes de script, nos van a permitir saber que métodos nos expone la aplicación.


NSZone, gestión de zonas de memoria en OSX

abril 21, 2010

Es posible que aunque desarrolleis aplicaciones en Objetive-C nunca os habeis visto en la necesidad de usar las zonas de memoria. Normalmente estos mecanismos se hacen de forma transparente y no tenemos que preocuparnos por ello. Pero si sois curiosos os llamará la atención como se asigna la memoria en las tripas del runtime del Objetive-C.

Al igual que mencionamos en el otro post de gestión de memoria en OSX, estos mecanismos, gracias al uso del garbage collector estan pasando a ser cosa del pasado. Ya que si usamos zonas con el recolector de basura activado, estas seran ignoradas. Sin embargo, y como dijimos en esa entrada, no siempre tenemos la posibilidad de desarrollar con el recolector de basura activado, como por ejemplo, si queremos que nuestra aplicación sea compatible con versiones anteriores de OSX o si desarrollamos para plataformas como iPhone o iPad donde esta funcionalidad aún no esta dispobible

Como se puede deducir del nombre del post, un NSZone representa una zona de memoria, que utilizaremos para alojar nuestros datos. Cuando creamos un programa, se crea una Zona por defecto, donde se alojaran todos nuestros objetos si no especificamos nada más, normalmente nos interesará crear y gestionar nuestra propia zona cuando vamos a hacer un uso especifico de dicha zona de memoria. Por ejemplo, si vamos a crear y liberar muchos objetos, quizas nos interese gestionar nuestra propia zona de memoria, en vez de realizar estas operaciones en la zona por defecto. Al alojar y desalojar memoria, siempre quedan zonas entre los diferentes objetos alojados que no son sencillas de reaprovechar, lo que aumenta la fragmentación, y teoricamente, empeora el rendimiento de nuestra aplicación. Además, y en teoria, los objetos de una zona estan próximos físicamente, con lo que si guardamos objetos relacionados entre si en la misma zona, el acceso desde uno a otro será mas rápido que si estan dispersos por la zona común.

Si teneis experiencia en C++, seguramente este concepto os recuerde a los “allocators” que se pueden utilizar cuando gestionamos ciertos objetos de STL.

Otro beneficio de la gestión manual de zonas es que si, colocamos N objetos en la memoria, será mucho mas rápido desalojarlos de nuestra propia zona que de la zona común del programa.

Hablando ya de ObjetiveC, la documentación es algo escasa, NSZone es una estructura de C, que guarda los datos referentes a la zona de memoria.

La función básica que nos sirve para crear una zona es:

NSZone * NSCreateZone (
NSUInteger startSize,
NSUInteger granularity,
BOOL canFree
);

A esta función le pasamos el tamaño inicial de la zona, el tamaño con el que crecerá o se encojerá en el parámetro granularity y le diremos si queremos que se libere memoria cuando se alojen nuevos objetos. Si le decimos que no, la reserva de memoria sera mas rápida ya que el algoritmo no tendrá que optimizar la zona cuando estemos reservando nueva memoria.

Para liberar una zona utilizaremos:

void NSRecycleZone (
NSZone *zone
);

Que no necesita mucha explicación, simplemente libera la zona que le pasamos como parámetro. La parte interesante, es que para prevenir problemas serios con objetos que esten en la zona cuando se Recicle, esta función, mueve los objetos vivos, de esta zona a la zona común.

Si queremos reservar memoria, explicitamente en una zona, podemos usar

void * NSZoneMalloc (
NSZone *zone,
NSUInteger size
);

Que nos reservara “size” bytes en la zona que le indicamos, devolviendonos un puntero a dicha porción de memoria.

Como podiamos esperar, la función que libera la memoria que acabamos de reservar es:

void NSZoneFree (
NSZone *zone,
void *ptr
);

Que es equivalente a lo que conocemos de “free” en C.

Todas estas funciones estan definidas en NSZone.h, dentro del Foundation framework

La gracia de las zonas, no es utilizar la memoria de esta forma “rudimentaria”, si no que, dicho concepto esta integrado completamente en el mecanismo de alocación de objetos en Objetive-C.

La clase padre de todas, NSObject, tiene dos metodos para alojar memoria, “alloc” y “allocWithZone:”.

De esta manera, cuando queremos crear un nuevo objeto, podemos usar esta variación de alloc, por ejemplo:

NSString * cadenaEnZona = [[NSString allocWithZone:mizona] init];

Que creará una cadena en la zona que le especificamos en lugar de en la zona común.

Debido a que este método esta en NSObject, cualquier objeto que creemos en nuestro proyecto, heredará este funcionamiento, y podremos instanciarlo en nuestra zona.

Además de este método allocWithZone, el otro método clave de NSObject para trabajar con Zones, es “copyWithZone:” que copiara el objeto especificado en otro, pero en la zona que le especificamos. Al igual que el método “copy”, el objeto a ser copiado, debe implementar la interfaz NSCopying, para asegurar que la copia del objeto se hace de forma adecuada.


QuickPost: plist de binario a texto

abril 13, 2010

Los ficheros plist (o property list) contienen información que habitualmente se suele utilizar como elementos de configuración en aplicaciones. Es en OSX donde su uso esta realmente extendido.

El contenido de estos ficheros esta codificado usando xml, sin embargo no siempre se guarda como texto en claro, es posible guardar estos ficheros en formato binario.

Una practica habitual es el configurar el comportamiento de una aplicación editando directamente estos ficheros. Como por ejemplo, editar los bookmarks de Safari editando el fichero ~/Library/Safari/Bookmarks.plist. Si el contenido del fichero esta guardado en formato binario, se hace imposible poder modificarlo con un editor de texto o con un simple script.

Para cambiar el formato de estos ficheros existe la utilidad plutil. Desde la terminal usaremos el sigiuente comando

plutil -convert xml1 Bookmarks.plist

Lo cual cambia el formato de Bookmarks.plist a texto en el propio fichero, es decir, no crea ninguno nuevo. Si queremos especificar una ruta, podremos usar la opción -o ruta de salida; o simplemente para sacarlo por la salida estandar.

Si queremos hacer el proceso contrario, convertir un fichero de texto a binario usaremos

plutil -convert binary1 Bookmarks.plist

Creando unidades virtuales en Mac

abril 11, 2010

Últimamente se ha visto nacer varios servicios de almacenamiento de ficheros online, o como algunos medios lo califican, almacenamiento en la nube.

Uno de los rasgos comunes de algunos de estos softwares es que crean una nueva unidad en nuestro sistema operativo, esta unidad virtual, es la forma de interactuar con el propio software.

Crear este tipo de aplicaciones a priori no parece sencillo, pues para crear una unidad hay que tratar con aspectos bastante complejos del sistema operativo.

Afortunadamente para nosotros existen varias librerías para ayudarnos en este cometido.

Hablando de las diferentes librerías podemos crear una linea que dividiría las soluciones para windows donde por ejemplo tenmos el opensource Dokan o el producto comercial de Eldos. Y del otro lado de la linea, estarían aquellas librerías para sistemas basados en Unix, como Linux o el propio OSX. Para estos sistemas un solo nombre sale a escena FUSE.

FUSE, además de ser el nombre de un fantastico emulador de Spectrum, es un modulo del kernel y una librería para el desarrollo de aplicaciones que quieran crear unidades virtuales.

En OSX, la versión de FUSE se llama MacFUSE, desde luego el nombre era fácil de adivinar 🙂

Desde el punto de vista de desarrollador, programar una aplicación que cree una unidad virtual usando una de estas librerías es infinitamente mas sencillo que lidiar con los apectos internos del sistema operativo.

El proceso de crear una de estas aplicaciones es muy similar en los tres sistemas operativos, las librerías utilizan un sistema de callbacks. Nuestra aplicación debe proporcionar una serie de funciones a las que la librería irá llamando según las operaciones del sistema operativo, operaciones como abrir, leer o escribir en un fichero, o abrir o crear directorios.

Concretamente usando MacFUSE, la estructura de punteros a funciones que atienden a los eventos habituales del sistema de ficheros es:

static struct fuse_operations hello_filesystem_operations = {
.getattr   = hello_getattr,
    /* To provide size, permissions, etc.             */
.open      = hello_open,
    /* To enforce read-only access.                   */
.read      = hello_read,
    /* To provide file content.                       */
.readdir   = hello_readdir,
    /* To provide directory listing.                  */
.listxattr = hello_listxattr, 
    /* To provide com.apple.{FinderInfo,ResourceFork} */
.getxattr  = hello_getxattr,
    /* To provide com.apple.(FinderInfo,ResourceFork) */
};

Aunque el ejemplo de aquí arriba es C, realmente se puede programar en muchos lenguajes, como no podia ser de otra manera MacFUSE además permite programar en Objective-C, pero ademas se puede programar aplicaciones usando otros lenguajes como Python, Ruby o Java, lo cual, hace que incorporar esta funcionalidad a nuestra aplicación sea más accesible.


Serialización usando Foundation Framework

abril 1, 2010

Un factor común en casi todos los programas que desarrollamos es que necesitamos guardar informacion que persista entre ejecuciones. A la hora de realizar esta tarea tenemos varias alternativas, una de las mas comunes es utilizar una base de datos, como podria ser SQLite, o también podemos usar ficheros XML.

Si nuestro programa esta desarrollado con un lenguaje orientado a objetos, es muy posible que lo que queramos grabar sean datos pertenecientes a un objeto, en este caso, hay multitud de APIs que nos permiten serializar los datos de un objeto para recuperarlos posteriormente. Hablando de Cocoa, la plataforma nos brinda un elegante método para conseguir este objetivo.

Lo primero que debemos hacer con el objeto que queremos serializar es hacerle que implemente la interfaz “NSCoding“, al implementar esta interfaz, debemos sobrecargar los métodos initWithCoder y encodeWithCoder. Estos dos mensajes se llamarán cuando queramos guardar el objeto a disco, o cuando estemos reconstruyendolo desde disco.

Cuando una de estas dos cosas ocurre, el funcionamiento típico es que cada objeto va sucesivamente enviando dichos mensajes a los atributos que componen el objeto. De esta manera si nuestro objeto tiene objetos del tipo NSString o NSInteger, debemos pasarles el mensaje a dichas clases. La ventaja es que los tipos básicos de Cocoa como NSString o NSArray ya implementan esta interfaz.

Como ejemplo vamos a serializar una clase que guarda la información de un correo electrónico:

@interface EMail : NSObject <NSCoding>
{

	NSString *		subject;
	NSDate *		mailDate;
	MailContact * 	sender;
	MailContact *	recipient;

	// MailContact es una clase que consta de dos cadenas, nombre y dirección.
}

Además de estos métodos, el framework Foundation nos proporciona tres clases básicas, estas clases son NSCoder y NSKeyedArchived / NSKeyedUnarchiver.

Con NSCoder podremos codificar objetos para ser guardados. Cuando el framework invoca a nuestros métodos de serialización, nos pasara una instancia de la clase NSCoder lista para utilizarla. La clase NSCoder nos proporciona métodos para codificar los tipos básicos como int, float o booleanos, pero tambíen nos proporciona el método encodeObject:, con el que codificaremos los objetos que forman el conjunto de atributos del objeto que tratamos de serializar.

De esta manera nuestros métodos serán algo como:

- (void)encodeWithCoder:(NSCoder *)coder
{
	[coder encodeObject:subject forKey:@"mailSubject"];
	[coder encodeObject:mailDate forKey:@"mailDate"];
	[coder encodeObject:sender forKey:@"mailSender"];
	[coder encodeObject:recipient forKey:@"mailRecipient"];
}

- (id)initWithCoder:(NSCoder *)coder
{
	subject = [[coder decodeObjectForKey:@"mailSubject"] retain];
	mailDate = [[coder decodeObjectForKey:@"mailDate"] retain];
	sender = [[coder decodeObjectForKey:@"mailSender"] retain];
	recipient = [[coder decodeObjectForKey:@"mailRecipient"] retain];

	return self;
}

Como vemos el funcionamiento es bastante sencillo.

Solo nos queda una última pieza en el puzle, y esta es guardar y recuperar los objetos. Para ello entra en juego NSKeyedArchiver y NSKeyedUnarchiver. Estas clases tienen dos métodos que van a realizar esta tarea, estos métodos son: “archiveRootObject:toFile:” y “unArchiveObjectWithFile:“. Al primero le pasaremos el objeto que queremos guardar y el fichero donde lo guardaremos. Mientras que al segundo le pasaremos el fichero y nos devolverá una instancia del objeto.

El código para realizar esto queda algo como:

MailContact* senderContact = [[MailContact alloc] initWithAddress:@"sender@sender.com" Name:@"Sender Contact"];

MailContact* recipientContact = [[MailContact alloc] initWithAddress:@"recipientrecipient.com" Name:@"Recipient Contact"];

EMail* emailPreArchived =
[[EMail alloc] initWithSubject:@"Email de prueba" date:[NSDate date]  sender: senderContact recipient:recipientContact];

NSLog(@"Email created:\n %@\n", emailPreArchived);
NSLog(@"Archiving Object\n");
[NSKeyedArchiver archiveRootObject:emailPreArchived toFile:@"/tmp/pruebaArchive"];
NSLog(@"Retrieving object from storage\n");

EMail* emailRetrieved;
emailRetrieved = [NSKeyedUnarchiver unarchiveObjectWithFile:@"/tmp/pruebaArchive"];
NSLog(@"Email retrieved:\n %@\n", emailRetrieved);

Como vemos guardar nuestros objetos usando el framework Foundation es bastante sencillo, si bien si nuestra estructura de objetos es mas compleja, los metodos de codificación y decodificación serán algo mas complejos. Quizás la principal desventaja de usar este tipo de métodos es la portabilidad, ya que, en algunos casos leer los objetos serializados desde otro entorno diferente es realmente dificil.