Copiar la ruta del fichero seleccionado en Finder usando Ruby

septiembre 11, 2011

Tras hablar sobre varios temas, en la última @nscoder_mad acabamos hablando del famoso lenguaje AppleScript. Personalmente es un lenguaje que he intentado varias veces aprender, pero nunca acaba de gustarme. Una de las conclusiones a las que llegamos es que es un lenguaje para no programadores. Quizás uno de los aspectos que no me gusta del lenguaje es que no veo a primera vista los diferentes elementos que conforman el código escrito con otros lenguajes como por ejemplo las palabras reservadas o incluso las propias sentencias en si mismo.

Un ejemplo de Applescript podría ser algo así:

tell application "Finder"
	set theFolder to make new folder at desktop
end tell

Sin duda algo que no se puede negar es su “verbosidad”, cualquiera que lo lea, mas o menos sabe lo que hace este mini-script. Pero, desde mi punto de vista, aunque muy literario, no me resulta cómodo leer código escrito de esta manera.

Pese a que a priori, puede parecer una opción muy asequible para gente que no ha programado en otros lenguajes, lo cierto es que incluso para ellos, crear algo nuevo en Applescript no es tan sencillo, en el fondo es un lenguaje de programación, y no entiende cualquier frase inglesa que podamos escribir.

Sin duda uno de los grandes beneficios de Applescript es su integración dentro de OSX, y como es capaz de interactuar con el sistema y el resto de aplicaciones de una forma muy sencilla.

De hecho, @j4n0 me comentaba que el tenía un script para copiar al portapapeles la ruta del fichero seleccionado en la ventana del Finder, cosa que es bastante útil.

Según lo expuesto hasta ahora, nuestra situación es que pese a que Applescript no parece un lenguaje muy atractivo pero es una “joya” a la hora de interactuar con el sistema operativo.

Por suerte hay una alternativa, gracias al ScriptingBridge, y como hablamos aquí y aquí, podemos usar otros lenguajes de programación como Ruby o Python para acceder a los mismos recursos que Applescript.

Actualmente mi situación es, me gustaría aprender Applescript por curiosidad, pero ¿para qué?. Lo mismo se puede hacer con Ruby, y es un lenguaje que ya conozco 🙂

Continuando con el caso anterior, el código en Ruby para copiar al portapapeles el fichero seleccionado en el Finder sería algo como:

require 'osx/cocoa'
include OSX
OSX.require_framework 'ScriptingBridge'

finder = SBApplication.applicationWithBundleIdentifier_ "com.apple.finder"
fileURL = NSURL.URLWithString_ finder.selection.get[0].URL
system "echo #{fileURL.path} | pbcopy"

Seguramente el código en Applescript ocupa menos lineas, pero al menos para mi, la versión en Ruby es mucho mas clara.

Como breve resumen al código, con la función applicationWithBundleIdentifier_ obtenemos la referencia al objeto “Finder” y usando el comando de consola pbcopy introducimos los datos al clipboard.

Pese a que funciona sin problemas desde la linea de comandos, este script es sobre todo útil cuando lo tenemos en la propia ventana del finder, para ello necesitamos encapsular el script en un bundle de Aplicación que OSX sepa como ejecutar.

Para ello, y aunque podamos hacerlo a mano, existe una utilidad llamada Platypus que nos automatizará el proceso. Una vez que tengamos el bundle solo nos queda arrastrarlo a la barra superior del finder y usarlo siempre que queramos.

Anuncios

Mensajes entre clases usando NSNotificationCenter

abril 3, 2011

La orientación a objetos de los lenguajes actuales hace que nuestros programas estén hechos de clases, clases que se comunican entre ellas mediante mensajes. Estos mensajes según el lenguaje se manifiestan en llamadas a métodos y en otros casos, como es el de ObjetiveC, como realmente mensajes.

Seguramente en muchas ocasiones, nos hemos encontrado con el caso de que queremos recibir un mensaje, pero el objeto que lo envía, no necesariamente tiene que tener una referencia nuestra, o visto desde el otro lado, queremos enviar un mensaje y no sabemos quien estaría interesado en recibirlo, e incluso, ese alguien no guarda ninguna relación con nuestra clase, por lo que no seria correcto que tuviera una instancia nuestra.

Pongamos un ejemplo, tenemos una clase que se encarga de gestionar una comunicación, y por otro lado, en la parte de la vista que muestra la información que viene de esa comunicación, tenemos una tabla que muestra datos. Cada vez que se recibe un mensaje nuevo, la tabla tiene que repintar los datos.

Podríamos tener una instancia de la clase de comunicación en nuestra vista, pero estaría ligando dos clases que excepto por ese detalle, no tienen nada mas en común.

Una solución seria utilizar una capa intermedia, que podría ser el Modelo que propagara las notificaciones de inserción de datos a todos los niveles. Esta solución sería la que actualmente implementaríamos en muchos sistemas, pero requiere meter eventos y orientar nuestro diseño hacia este tipo de dependencias entre las clases.

La solución a la que se puede llegar con las notificaciones es desacoplar totalmente todas las clases del ejemplo. Usando este modelo, la clase de la vista se registraría en la cola de notificaciones diciendo que le interesa escuchar cuando llega un nuevo mensaje. Por su parte la clase que se encarga de controlar la comunicación, “posteará” un mensaje cuando reciba un mensaje.

De esta forma, todos los elementos están completamente desacoplados, y en el fondo, nadie necesita tener una instancia de otras clases. Incluso, si en algún momento una de ellas dejaría de existir, las otras clases podrían seguir funcionando sin problemas.

Después de todo este rollo ;), vamos a ver como es de fácil hacerlo en Cocoa y la cantidad de ventajas que aporta este enfoque en el diseño de nuestras clases.

En Cocoa y Cocoa Touch, existe una clase llamada NSNotificationCenter, dicha clase se va a encargar de gestionar un “centro” de notificaciones que es accesible desde todas las clases de nuestro programa, e incluso desde clases de diferentes programas.

El runtime creará el centro de notificaciones por nosotros, y por lo tanto accederemos a la clase usando un método estático.

Básicamente hay dos formas de interacción con el centro de notificaciones, por un lado, los “observers” se añaden como receptores de la notificación usando un nombre determinado. Por otro lado, los notificadores, notifican a los observadores usando ese nombre como clave.

Hay varios métodos para registrarse como observador, por ejemplo el método, addObserver:selector:name:object:, especifica la instancia, el selector de la clase que se ejecutará, el nombre de la notificación y opcionalmente la instancia que será capaz de enviar la notificación, si este parametro lo dejamos en “nil” no filtraremos por el objeto que envia la notificación. El otro método para añadirse como observador es: addObserverForName:object:queue:usingBlock:, donde en lugar de indicar un selector, se pasa un bloque de código que se ejecutará cuando se reciba una notificación. En este último método vemos el parámetro “queue”, que trataremos mas adelante.

Por otro lado, para “postear” una notificación, podemos usar dos métodos, postNotificationName:object:, donde indicamos el nombre de la notificación que estamos “levantando”, o bien, podemos usar el método, postNotification:, donde agrupamos la misma información en un objeto de tipo NSNotification.

Finalmente, los observers deben quitarse del centro de notificaciones cuando ya no quieren recibir más. Para ello se puede usar el método removeObserver: o removeObserver:name:object:.

Vamos a ver un ejemplo completo en acción.

/* Código de un UIViewController que ademas es un UITableViewDataSource */

- (id) viewDidLoad
{
  // ...
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  [center addObserver:self selector:@selector(logOn:) name:@"logged on" object:nil];

  // ...
}

- (void) logOn: (NSNotification*) theNotification
{
    // La clase encargada de la comunicación nos ha notificado de que
    // se ha producido la entrada al sistema
}

- (void)viewDidUnload
{
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  [center removeObserver:self];
}

/* Codigo de la clase que se encarga de gestionar la comunicación */

// ...
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  [center postNotificationName:@"loggedon" object:nil];
  // En este momento ya se habra ejecutado el codigo del ViewController que estaba
  // apuntado para recibir notificaciones.
// ...

Una duda que nos puede surgir es, como se ejecuta el código asociado a la notificación cuando el notificador postea un mensaje.

Realmente el código se ejecuta de forma síncrona y en el contexto de la instancia que se pasa cuando un observador se registra en el NotificationCenter, de esta manera, cuando el notificador postea el mensaje usando una de las variaciones del postNotification, se bloquea hasta que todos los métodos de los obsevers se ejecutan. Sin embargo, por si queda alguna duda, el método addObserver no bloque a la clase que se llama, el único momento de sincronismo se produce por parte de la clase que envía la notificacion.

Para que esta ejecución sea asíncrona, debemos usar colas de notificación, y si volvemos atrás, el parámetro queue del método postNotification es el que gestiona este detalle.

Así que os dejo con la intriga :D, y para dos futuros posts hablaremos de las notificaciones asíncronas usando colas de notificaciones y como enviar notificaciones entre procesos usando el NSDistributedNotificationCenter.


Probando clases ObjetiveC usando MacRuby

febrero 12, 2011

Sin duda una de las cosas que siempre me ha gustado tener a mano es una manera de probar código sin tener que crear todo un proyecto para ello. Hace mucho tiempo, cuando usaba Java como mi principal lenguaje de programación, usaba Beanshell, que ademas de ser un lenguaje de scripting ofrece una consola que interpreta código en java en tiempo real.

Por aquel tiempo, si quería probar, por ejemplo, como sacar una subcadena a partir de un carácter dado, podía ir probando con las diferentes combinaciones en la consola de Beanshell. La alternativa era construirse todo un proyecto solo para probar como deberías hacer el substring correcto, proyecto que había que compilar y ejecutar para ver los resultados. Sin duda el hecho de usar una consola que te daba los resultados inmediatos era una gran ventaja.

Mas adelante, y con algunas aventuras en python o ruby, la existencia de la consola para probar código venia de serie con la plataforma. Así si queremos probar un pequeño fragmento de código Ruby, podemos arrancar el irb y escribirlo tal cual.

Hoy en día, en mis aventuras por Objetive-C, echaba de menos esta funcionalidad. Esta claro que al ser Objective-C un lenguaje compilado, a priori es complicado tener un programa que haga esto, pero es aquí donde encaja esa maravilla llamada MacRuby 🙂

Dado a que, usando MacRuby, podemos acceder a todas las clases definidas en los frameworks de OSX sin importar en que lenguaje estén definidas, vamos a poder usar el irb para poder probar esas clases de Cocoa que usamos habitualmente.

Vamos a ver un ejemplo:

Supongamos que tenemos una cadena de la forma “clave=valor;clave2=valor2″” y queremos transformarla en un diccionario que almacene esta información.

Para ello, necesitamos manipular la clase NSString, sin duda una alternativa sería escribir el código (siempre después de los test, claro 😉 ), e ir probando. Pero siempre surgen algunas dudas, como por ejemplo, el IndexOf de NSString, ¿esta basado en 0?, si hago un substring con inicio en la posición 3, ¿incluye el tercer carácter?

Estas dudas, harán posiblemente que el código que escribamos la primera vez, falle.

La alternativa es arrancar macirb, y jugar directamente con la clase NSString. Por ejemplo si ejecutamos lo siguiente:

irb(main):010:0> "clave=valor;clave2=valor2".componentsSeparatedByString(";")
=> ["clave=valor", "clave2=valor2"]

Veremos que el mensaje “componentsSeparatedByString”, funciona como esperábamos. Pero no solo eso, vemos que también podíamos aplicar lo mismo a cada elemento para obtener cada clave y cada valor:

irb(main):012:0> "clave=valor;clave2=valor2".componentsSeparatedByString(";").each { |x| p x.componentsSeparatedByString("=") }
["clave", "valor"]
["clave2", "valor2"]
=> ["clave=valor", "clave2=valor2"]

Como vemos, la comodidad que ofrece el probar código solo con teclearlo directamente en la consola es tremendamente util para reducir muchos ciclos de compilar (o ejecutar test) y ver como vuelve a fallar.

La “magia” de MacRuby no acaba aquí, como hemos dicho antes, podemos llamar a cualquier clase de Cocoa, por lo que si probamos el siguiente código:

irb(main):006:0> framework 'Cocoa'
irb(main):007:0> sp = NSSpeechSynthesizer.alloc.initWithVoice nil
=> #<NSSpeechSynthesizer:0x2005293e0>
irb(main):008:0> sp.startSpeakingString "hello from macruby. This is so cool!"
=> true

Nuestro mac empezará a hablar. 😀

Como veis, la única regla a seguir es en vez de utilizar la sintaxis típica de objetive c para enviar mensajes, debemos utilizar la notación habitual con el “.”. Aunque si esto nos incomoda, siempre podemos seguir usando mensajes gracias al método “send” de Ruby, y de esta manera ejecutar el anterior código como:

irb(main):010:0> sp.send :startSpeakingString, "hello from macruby. This is so cool!"

La interoperabilidad Ruby Cocoa abre todo un mundo de posibilidades, ¿Alguien ha dicho algo sobre RSpec para probar clases hechas en ObjectiveC?. En cualquier caso, son unos interesantes temas para otras entradas del blog.


Construyendo un Servicio en OSX

enero 16, 2011

Uno de los aspectos que pasan mas desapercibidos en OSX son los “Servicios”. Seguramente por el nombre pensareis que es un programa que se ejecuta de fondo en nuestro ordenador.

Si bien ese pensamiento no es del todo incorrecto, los servicios en OSX es algo mas. Con servicios me refiero a los elementos del menú que sale cuando pinchamos sobre el nombre del programa activo en la barra de menús.

La localización de dicho menú ha ido cambiando con el tiempo, en versiones anteriores ocupaban una posición mas destacada, pero poco a poco han ido pasando a un plano mas “oculto”.

Sin embargo, la posición de esta funcionalidad no nos tiene que despistar, ya que la utilidad que prestan es bastante buena.

Para comprobar su funcionamiento, solo tenemos que seleccionar algo de texto, e ir al menú Servicios. Desde dicho menú se nos dará la opción de hacer diversas cosas, como por ejemplo, crear una nota. Además no solo los servicios están accesibles desde este menú, si no que podremos acceder usando atajos de teclado. Por ejemplo, si pulsamos SHIFT+ ⌘+Y, crearemos la nota al instante.

Una de las grandes ventajas de los servicios es que están disponibles para todo el sistema, esto es, independientemente de la aplicación donde seleccionemos el texto, si pulsamos el atajo de teclado, crearemos una nota con el texto seleccionado.

Como podemos leer en la propia documentación de Apple, posibles servicios serian, reconocedores de texto en una imagen, cifrado de texto o incluso, aunque no venga en la guía de Apple, sería útil un servicio para insertar una cita de Chuck Norris en el documento actual 🙂

Desde el punto de vista de la programación, se pueden distinguir dos elementos, por un lado el propio servicio, y por otro la aplicación que interactua con él. La pieza que intercomunica ambas partes es el clipboard. Cuando pinchamos en un servicio, la aplicación pone los datos necesarios en el clipboard, e invoca al servicio. El servicio por su parte, recoge dichos datos, los procesa y los vuelve a poner en el clipboard. Finalmente, la aplicación recoge lo que haya en el clipboard y hace lo que considere oportuno con dichos datos.

En la entrada vamos a crear un servicio, que seleccionado texto, lo consulte en la wikipedia usando Safari.

El código que atiende al servicio, es sencillo, a grandes rasgos, los pasos que hay que dar son por un lado, crear el método al que se llamará, y por otro, registrar el servicio en el sistema.

Desafortunadamente, xcode no tiene plantillas para crear los servicios, por lo que tendremos que empezar desde un proyecto vacío que cree un ejecutable.

Una vez que tenemos el proyecto creado en el XCode, debemos añadir (si no lo estaba antes), el framework de Cocoa y Foundation, y añadir la clase que implementará el método que atiende el servicio.

En nuestro caso, dicha clase es WikipediaService


==== WikipediaService.h

#import <Cocoa/Cocoa.h>


@interface WikipediaService : NSObject {

}

- (void) wikipediaSearch: (NSPasteboard*) pboard 
				userData:(NSString*) theUserData 
				   error:(NSString**) theError;

@end

==== WikipediaService.m

@implementation WikipediaService

- (void) wikipediaSearch: (NSPasteboard*) pboard userData:(NSString*) theUserData error:(NSString**) theError
{
	// Test for strings on the pasteboard.
	NSArray *classes = [NSArray arrayWithObject:[NSString class]];
	NSDictionary *options = [NSDictionary dictionary];
	
	if (![pboard canReadObjectForClasses:classes options:options]) {
		*theError = NSLocalizedString(@"Error recuperando datos del pboard", 
									  @"pboard couldn't give string.");
		return;
	}
	
	// Get and encrypt the string.
	NSString *pboardString = [pboard stringForType:NSPasteboardTypeString];
	NSString *wikipediaString = [NSString stringWithFormat:@"http://en.wikipedia.org/wiki/%@", pboardString];
	NSURL *url = [[NSURL alloc] initWithString: wikipediaString];
	[[NSWorkspace sharedWorkspace] openURL:url];
	
	[pboard clearContents];
}

@end

Como vemos, el clipboard es la parte que comunica ambos procesos. Con el mensaje canReadObjectForClasses primero comprobamos si el clipboard contiene datos de tipo cadena, y luego con el mensaje stringForType del “pasteboard” obtenemos dicha cadena.

Por otro lado, en nuestro punto de entrada de la aplicación, debemos situar el código que registra el servicio en el sistema.

int main(int argc, char *argv[])
{

	NSRegisterServicesProvider([WikipediaService new], @"WikipediaService");
	NSUpdateDynamicServices();
	[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
	return 0;
}

Antes de ejecutar y probar nuestro servicio, nos queda una parte muy importante. En el fichero plist de la aplicación, debemos indicar los datos referentes al servicio. Que indicará al sistema como llamar y como mostrar la información asociada.

	<key>NSServices</key>
	<array>
		<dict>
			<key>NSMenuItem</key>
			<dict>
				<key>default</key>
				<string>Wikipedia Search</string>
			</dict>
			
			<key>NSKeyEquivalent</key>
            <dict>
                <key>default</key>
                <string>R</string>
            </dict>
			
			<key>NSMessage</key>
			<string>wikipediaSearch</string>
			
			<key>NSPortName</key>
			<string>WikipediaService</string>
			
			<key>NSSendTypes</key>
			<array>
				<string>NSStringPboardType</string>
				<string>NSPasteboardTypeString</string>
			</array>			
		</dict>
	</array>

Si os fijáis, en el campo, NSKeyEquivalent, podemos asignar una tecla, que sumada a Command, sera el shortcut para llamar a nuestro servicio. Si dicha letra esta en mayúsculas, ademas necesitaremos pulsar Shift.

Con todo ya terminado, solo nos queda instalarlo, para ello debemos copiar el “Bundle” que ha generado nuestro proyecto en XCode a ~/Library/Services. Para que el servicio sea reconocido, debemos cerrar sesion y volverla a inciar, pero existe una forma manual para forzar al sistema para que reconstruya el indice de servicios.

Esto es ejecutando el siguiente comando: /System/Library/CoreServices/pbs

Una vez que tenemos nuestro servicio instalado, solo tenemos que seleccionar cualquier texto para buscarlo en la wikipedia.


Manejando Safari desde Objetive-C

enero 2, 2011

Recientemente me surgió la necesidad de controlar remotamente un explorador web en Windows. En el sistema operativo de Microsoft, gracias a los objetos COM, es posible “instanciar” un objeto InternetExplorer y controlarlo desde código fuente. Gracias a .NET y la Interoperabilidad con los objetos COM, la tarea inicialmente no parece ser para nada complicada, aunque como la compañía de Redmond nos tiene acostumbrados, lidiar con los detalles será una tarea que nos llevará bastante tiempo.

En OSX por su parte este objetivo también esta cubierto, y como pasa habitualmente, de una forma bastante más elegante :D.

La idea es poder controlar, y crear instancias de una aplicación que esta ejecutandose, o que vamos a iniciar. Para ello, como requisito imprescindible, la aplicación debe estar preparada para ser manejada.

El componente clave para que todo esto se pueda realizar son los denominados “Apple Event”, que son paquetes de información que se envían en forma de mensaje entre las diferente aplicaciones que se están ejecutando en el Sistema Operativo.

Por ejemplo, si queremos que el Safari en ejecución navegue a una URL, y eso lo queremos hacer desde nuestra aplicación, deberemos enviar un Apple Event que contenga los datos necesarios para llevar a cabo esa acción.

Originalmente, solo era posible enviar eventos usando AppleScript, de hecho, el lenguaje se creo alrededor de dichos eventos. Pero hoy en día se puede utilizar multitud de lenguajes para enviar “Apple Events” como Python, C#, Ruby o Javascript.

Lo que hace posible esta interoperabilidad de lenguajes es lo que se conoce como el Scripting Bridge, como ya hablamos en el blog cuando queríamos usar Python para controlar el iTunes.

A diferencia de la entrada anterior, en esta entrada, vamos a usar Objecitve-C para controlar el Navegador Web, dado que ObjC es un lenguaje compilado, necesitamos tener una referencia de lo que podemos hacer con el “objeto” Safari, de tal manera que podamos compilar nuestro código.

Para ello usamos el comando sdp, que generará un fichero .h y que usaremos para compilar nuestro código. La entrada del comando sdp son los ficheros .sdef, que contienen, en forma de XML, una descripción sobre lo que se puede hacer con el objeto en cuestión.

A nivel de controlar el Navegador, una de las cosas mas utiles es que podemos ejecutar código javascript, con lo que tenemos acceso a todo el documento web que estamos visualizando.

A continuación os dejo un fragmento de código que navega a google, realiza una busqueda usando javascript y muestra el numero de resultados encontrados.

SafariApplication* safari = [SBApplication 
  applicationWithBundleIdentifier:@"com.apple.Safari"];
	
SBElementArray* windows = [safari windows];
SafariTab* currentTab = [[windows objectAtIndex: 0] currentTab];
	
[currentTab setURL:@"http://www.google.com"];
[safari doJavaScript: 
 @"document.getElementsByName('q')[0].value = 'Safari'" 
				  in:currentTab];
[safari doJavaScript: @"document.forms[0].submit()" 
				  in: currentTab];
id result = [safari 
   doJavaScript:@"document.getElementById('resultStats').firstChild.data"
           in:currentTab];
NSLog(@"Results: %@", (NSString*)result);

Un uso en el que esta técnica es útil es cuando queremos hacer pruebas automatizadas de alguna web que estemos realizando. Aunque para estas tareas existe selenium, que si utilizamos firefox, seguramente cubra nuestras necesidades.


Métodos privados en Objetive-C

julio 25, 2010

Si programamos habitualmente en Objetive-C seguramente nos habremos encontrado con la necesidad de crear un método que sea solo accesible desde dentro de la clase, vamos, un método privado 🙂

Seguramente, en este momento recordemos como se hace en otros lenguajes, pero no podemos recordar como se hacia en este lenguaje. El motivo de esta falta de recuerdo es por que realmente no nos habremos encontrado una sección sobre ello en el libro o web que usamos para aprender este lenguaje.

Si bien existe la habituales directivas de ámbito @private, @public y @protected de compilador, solo tienen aplicación en los atributos de la clase, y no en los métodos. Además al igual que pasa con otros lenguajes estas directivas solo se aplican en el compilador, pero en el caso de Objetive-C, si accedemos a estas variables privadas, solo obtendremos un warning, como el que vemos en la imagen.

Pero entonces ¿como definimos métodos privados?. Antes de ello vamos a repasar que contiene un fichero de implementación.

Los ficheros .m habitualmente contienen la definición de los métodos que se han declarado en el fichero .h. Como seguramente sabremos, dichas definiciones están comprendidas entre las directivas @implementation CLASE y @end. ¿Que pasa si intentamos definir un método fuera de este bloque?. Pues depende ;), si intentamos definir un método de la clase, el compilador no nos dejara, pero si por otro lado tratamos de definir una función de C o C++, el compilador no pondrá ningún problema. De esta manera podemos pensar que la sección entre @implementation y @end es la parte Objetive-C del fichero .m. Fuera de esta sección no podremos acceder a cosas como “self”, aunque si podremos meter sin problemas código en Objetive-C. Por ejemplo, veamos el contenido del siguiente fichero .m.

int sumaC(int a, int b) {
	PruebaPrivado *pp = [[PruebaPrivado alloc] init];
	return [pp suma:a with:b];
}

@implementation PruebaPrivado
-(int) suma: (int) op1 with: (int) op2 {
	return op1 + op2;
}

-(int) suma2: (int) op1 with: (int) op2 {
	return sumaC(op1, op2);
}

@end

En Objetive-C se sigue una filosofía parecida. La primera aproximación de método privado pasa por declarar esa función únicamente en el fichero .m, y no en el .h. De esta manera los clientes de la clase importarán en fichero .h, y no sabrán de la existencia de los métodos privados. Eso si, como hemos visto, si queremos acceder al contenido de la clase, debe estar definida dentro del bloque de implementación. Como podemos ver en el siguiente ejemplo.

@implementation PruebaPrivado

-(int) suma: (int) op1 with: (int) op2 {
	return [self sumaPrivada: op1 with: op2];
}

-(int) sumaPrivada: (int)o1 with: (int) o2 {
	return o1 + o2;
}

@end

Sin embargo, esta forma de hacerlo tiene sus inconvenientes, por un lado, si compilamos el código del ejemplo el compilador nos dará un warning. Este warning viene de que sumaPrivada no esta definido en el momento de compilar el método suma. Por lo que el compilador nos informa de que PruebaPrivado puede no responder al mensaje sumaPrivada. Este warning se solucionaría poniendo la declaración del método antes de su uso, como pasa habitualmente con C.

Pero aún sin el warning, otro principal inconveniente es la legibilidad. Si intercalamos métodos privados con públicos, mas adelante puede ser un follón revisar nuestro propio código, ya que no tendremos agrupadas todas las funciones privadas de nuestra clase.

Aunque las solución anterior presentaba ciertos inconvenientes, sienta las bases de la solución que habitualmente se suele usar. Con la incorporación de las categorías en Objetive-C se abre la posibilidad de modificar una clase “a posteriori” como ya vimos en esta otra entrada.

La solución pasa por crear una categoría en el fichero .m de implementación, que no estará visible por ningún cliente y que agrupara los métodos privados, de esta manera todos los métodos estarán declarados en el mismo lugar, y nos quitaremos tanto el problema del warning del compilador como la desorganización.

Por tanto, el código quedara como:

@interface PruebaPrivado ()

-(int) sumaPrivada:(int)o1 with:(int)o2;

@end


@implementation PruebaPrivado

-(int) suma: (int) op1 with: (int) op2 {
	return [self sumaPrivada: op1 with: op2];
}

-(int) sumaPrivada: (int)o1 with: (int) o2 {
	return o1 + o2;
}

@end

Como vemos, ahora sumaPrivada esta declarada dentro de la categoría, que además como habéis visto, no lleva nombre. Este hecho nos aporta alguna ventaja como es que la definición de los métodos puede ir en la misma sección de implementación que los métodos públicos, en lugar de tener que crear una sección especifica para esta categoría.


Key-Value coding

julio 17, 2010

Seguramente en muchos de vuestros desarrollos os habreis encontrado con las típicas clases que contienen únicamente datos. Por ejemplo, en Java estas clases se llaman POJOs. Normalmente la forma de acceder al contenido de estos datos es usando los métodos de acceso, también llamados getters y setters.

El Key-Value coding (KVC), nos va a permitir acceder a estos datos de forma un poco diferente, y en cierta manera, mucho mas útil y dinámica.

El uso típico de lenguajes como Java o C# es utilizar unicamente los métodos de acceso, sin embargo en otros entornos como los lenguajes de script, se nos abren otras posibilidades para acceder a los datos que encapsula una clase. Con los ultimos cambios que se incorporaron a Objetive-C, el mecanismo para acceder a dichos datos, está mas cerca de los lenguajes de script que de los lenguajes compilados tradicionales.

Gracias a KVC podremos acceder a las variables usando únicamente su nombre, de tal manera que el propio runtime se encargará de llamar a los métodos de acceso adecuados. La forma de acceder a estas variables es usando un par de métodos genericos, getValueForKey: y setValue:forKey:.

La pregunta es. ¿Y para que necesitamos hacer esto de esta manera en lugar de usar los tipicos getters y setters?. Para responderla imaginemonos la siguiente situación, tenemos un conjunto de nombres de propiedades almacenados en un array, si queremos acceder a las propiedades usando los nombres, con estos métodos el acceso sería directo, sin embargo, con el acceso tradicional deberiamos “montar” un conjunto de ifs para analizar cada caso. En código sería algo así:

/* SIN KVC */
// array tiene ["altura", "peso", "edad"]
for (NSString* prop in array) {
	if ( [prop isEqual:@"altura"] ) {
		valor = [persona altura];
		NSLog(@"El valor de altura para la persona es %f", valor);
	} else if ( [prop isEqual:@"peso"] ) {
		valor = [persona peso];
		NSLog(@"El valor de peso para la persona es %f", valor);
	} else if ( [prop isEqual:@"edad"] ) {
		valor = [persona edad];
		NSLog(@"El valor de edad para la persona es %f", valor);
	}
}

/* CON KVC */
for (NSString* prop in array) {
	valor = [persona getValueForKey:prop];
	NSLog(@"El valor de %@ para la persona es: %f", prop, valor);
}

Hablando de un caso práctico, el tipico ejemplo es para rellenar los datos de una tabla, el método encargado de devolver el valor de una columna obtiene el nombre de dicha columna, que habitualmente se corresponde con una propiedad de una clase, usando KVC, el método puede devolver el valor de la propiedad de forma directa. Como vemos en el siguiente fragmento de código

- (id)tableView:(NSTableView *)tableview 
  objectValueForTableColumn:(id) theColumn
                        row:(int) theRow
{
    Persona *p = [arrayPersonas objectAtIndex:theRow];
	return [p valueForKey:[theColumn identifier]];
}

De hecho, este mecanismo de acceso a propiedades es el que se utiliza cuando realizamos un binding en el Interface Builder. Automaticamente liga el valor del control a la propia propiedad del objeto, de tal manera que hacer que una vista muestre el contenido de una clase es bastante trivial.

Y, ¿Cómo hago que mi clase soporte los mecanismos KVC?. Para responder a la pregunta, primero vamos a ver como funciona internamente los mecanismos de KVC. 😉

Cuando enviamos el mensaje getValueForKey: o setValue:forKey:, internamente se busca la existencia de un método de acceso que use la nomenclatura estandar, si por ejemplo, la key es “peso”, se buscará un par de métodos llamados “peso” y “setPeso”. Si existen, se usarían para acceder a los datos. En el caso de que no existan, se envía el mensaje accessInstanceVariablesDirectly, si este mensaje retorna “YES”, se procede a buscar la propia variable dentro del objeto. Si este procedimiento tampoco funciona, entonces se le envía al objeto el mensaje setValue:forUndefinedKey: como última instancia. La implementación por defecto de este método levanta una excepción. Pero el objeto puede sobrecargar esta implementación por defecto para provocar otro comportamiento. Realmente el proceso de acceso para un “get” es algo mas complejo que el que hemos expuesto, pero a grandes rasgos se podría simplificar como en el caso de los “set”

Volviendo a la pregunta, para que nuestra clase soporte el acceso KVC, la forma mas sencilla es proporcionar los métodos estandar. Para realizar esta tarea, tenemos varias opciones. Una de ellas, y las obvia, es hacernos nosotros a mano estos métodos, sin embargo, Objetive-C nos brinda la posibilidad de autogenerar este código.

En esta autogeneración entra en juego dos elementos, @property y @synthesize . @property nos servirá para declarar las propiedades en el fichero .h, mientras que @synthesize nos servirá para definir los métodos de acceso dentro de la sección de implementación. Por ejemplo:

// Persona.h
@interface Persona : NSObject {
}

@property (retain) NSNumber* peso;
@property (retain) NSNumber* altura;
@property (retain) NSNumber* edad;

@end

// Persona.m
@implementation Persona
@synthesize peso, altura, edad;

@end

Únicamente colocando ese código, el compilador generará por nosotros los métodos de acceso. Sin embargo, si necesitamos un procesamiento especial en el acceso a los datos de nuestra clase, tendremos que manualmente, implementar dichos getters y setters.

Seguramente os habreís fijado en el (retain) que esta incluido en la declaración de la propiedad. Lo que colocamos entre parentesis son los atributos de la propiedad e indica que se va a hacer la asignación de la propiedad. El tema es algo complejo y podría ser una entrada en el blog, por lo que de momento, nos podemos quedar en que existen tres tipos exclusivos entre si, retain, assign y copy. Y que hacen un retain sobre la variable, la asignan tal cual o la copian respectivamente.

Muy ligado a todo lo que hemos hablado, esta el acceso “simplificado” a estos campos que se introdujo recientemente en Objetive-C. Este acceso se conoce como “dot syntax”. Usando esta técnica podemos acceder a los datos simplemente poniendo un “.” entre la instancia que posee los datos y el nombre del dato, como por ejemplo:

persona.peso = [NSNumber numberWithInt:5];
NSLog(@"El peso es: %@", persona.peso);

Al igual que lo que hemos comentado antes, este acceso realmente esta enmascarando una llamada a setPeso: y peso, de la clase Persona.

Como veis, estos mecanismos simplifican el código, y aprovechandose del dinamismo de Objetive-C, proporcionan cosas tan importantes como son los bindings.