Doncs ara passarem a fer una mica de programació amb Objective-C per posar-nos al dia de la seva sintaxi i gramàtica. També mirarem de veure com funcionen les característiques principals d'aquest llenguatge i cap al final explicarem un parell de classes de framework Foundation.
Aquest tutorial està basat en el que es troba en aquesta pàgina. He escollit aquest pel fet de ser senzill i perquè hi he vist exemples.
Com ja he comentat en la breu explicació del principi, aquest tutorial està basat en el d'aquesta pàgina on el podeu trobar en anglès.
Per seguir aquest tutoria podeu fer-ho des de diferents Sistemes Operatius, doncs l'Objective-C està disponible amb codi lliure i existeixen diferents IDEs per a cada plataforma. Encara que un servidor utilitzarà la IDE XCode d'Apple podeu fer-ho amb les següents.
Aquest tutorial assumeix que tens alguns coneixement bàsics de C, incloent-hi els tipus de dades C, què és una funció, què és un valor de retorn, conèixer els punters i la gestió de memòria a C. Si no en tens massa, et recomanem que t'hi introdueixis abans de continuar.
L'Objective-C, com a derivat de C, hereta totes les característiques de C. Hi ha alguns excepcions però no es desvien massa del que ofereix C com a llenguatge.
nil: En C/C++, probablement, has utilitzar NULL. A Objective-C aquest és diu nil. La diferència és que tu pot passar missatges a nil (per exemple [nil missatge];) i això és perfectament correcte. No pots aconseguir el mateix amb NULL.
BOOL: C no té un tipus oficial de booleà, i en realitat l'Objective-C tampoc. Està inclòs dins de les classes Foundation (anomenades amb la importació de NSObject.h). nil també està inclòs en el fitxer de capçalera. BOOL a Objective-C té dos estats. YES i NO en comptes de TRUE i FALSE.
#import contra #include: Tal com veuràs en l'exemple de l'Hola món, s'utilitza #import. #import és bàsicament el mateix que #ifndef #define #endif a principi i al final de cada fitxer .h que creïs. Per tots els propòsits, només utilitza #import. No molesta tant, i no creien que ho treguin. Per altra banda, Apple oficialment utilitza #import en tot el seu codi, i no creiem que Apple estigui d'acord en que ho treguin.
Les paraules mètode i missatge s'utilitzen indistintament a Objective-C, encara que els missatges tenen propietats especials. Un missatge pot transmetre's dinàmicament a un altre objecte. Cridar un missatge d'un objecte a Objective-C no significa que l'objecte implementa aquest missatge, només que aquest coneix com respondre-hi ja sigui implementant-lo directament o transmetent el missatge a una altre objecte que sap com fer-ho.
Doncs au, anem a fer el nostre primer programa. El primer que hem de fer és obrir la nostra IDE, en el cas de Mac OS X l'XCode que si l'heu instal·lat a /Developer/Applications/Xcode.
Un cop el tenim obert l'XCode, només tenim un menú. Llavors passem a crear la nostra primera aplicació, creant un nou projecte. Aneu a File > New Project o premeu les tecles corresponents. Quan us surti l'assistent seleccioneu Command Line Utility i dins seu Foundation Tool. Després d'indicar el nom del projecte i el directori on es guardarà un oferirà una sèrie de fitxers amb un en particular que conté el codi. Exactament és el fitxer HolaMon.m depenent del nom que hi heu ficat al projecte. El codi d'aquest fitxer és el següent:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSLog(@"Hola, Món!");
[pool release];
return 0;
}
Bé, ara només cal que canvieu el text al català. ;-). Si el compilleu i l'executeu us sortirà una finestra amb els Logs del programa, i el missatge que hi heu ficat.
[Session started at 2005-08-20 13:00:56 +0200.] 2005-08-20 13:00:56.171 HolaMon[730] Hola, Món! Executable “Hola Mónâ€? has exited with status 0.
Però ja sé que em direu que això només funciona amb l'XCode, i en part teniu raó, dons l'XCode executa el compilador amb els paràmetres adients d'on agafar les llibreries i el fitxer resultant de la compilació. Clar que també podem mirar de fer-ho a mà des de la línia de comandes. Primer de tot executarem l'apliació des de la línia de comandes per comprovar que hem fet un programa de línia de comandes. Anem al directori del projecte i entrem en el directori build. A dins ja podem executar el programa ./HolaMon i ens retorna el següent:
2005-08-20 13:10:10.538 HolaMon[768] Hola, Món!
El següent pas és aconseguir compilar el programa des de la línia de comandes, per això comencem provant sort per si funciona gcc HolaMon.m. No hem tingut massa sort. Ens retorn errors d'enllaçat, on no troba algunes funcions de la llibreria Foundation. Doncs el següent pas és passar-li amb el paràmetre -framework on es troba la llibreria i el paràmetre -o per indicar el fitxer de sortida gcc -framework Foundation -o HolaMon HolaMon.m donant com a resultat:
2005-08-20 14:53:36.865 HolaMon[1235] Hola, Món!
Si a algú li interessa saber on trobar les instruccions exactes que executa l'XCode ho pot mirar en el recuadre central del menú Build > Detailed Build Result quan aquest compili.
Però pels que no teniu Cocoa, podeu fer la prova amb el codi següent, directament editant un fitxer hola.m amb un editor normal com vim, nano, o emacs.
#import <stdio.h>
int main( int argc, const char *argv[] ) {
printf( "Hola món\n" );
return 0;
}
I per compilar-lo, executeu gcc hola.m -o hola
Utilitzeu #import en comptes de #include a Objective-C
Per defecte la extensió del fitxer d'Objective-C és .m
Un cop provat que podem compilar i executar un petit programa, ja estem preparats per introduir-nos a la programació orientada a objectes amb Objective-C. Per això crearem un projecte nou on implementarem una classe Fracció que ens permeti treballar les fraccions amb numerador i denominador.
Us recomanem que feu una ullada al resum del llenguatge Objective-C per conèixer com funciona, i que el tingueu com a referència a mida que aneu desenvolupant programari.
Doncs som-hi, anem a l'XCode i creem un projecte nou de línia de comandes anomenat AppFraccio, com en el projecte anterior. L'XCode ens tornarà a crear el codi de l'Hola Món anterior que ja ens encarregarem després d'esborrar o substituir.
Per crear una classe nova anem al menu arxiu > Nou fitxer con ens sortirà un assistent d'on seleccionarem dins l'arbre l'opció Cocoa > Objective-C class. Ens demanarà el nom de la classe que l'anomenarem Fraccio i el lloc on es guardarà i amb quines aplicacions es compilarà (en un projecte es poden tenir diferents destins de la construcció amb diferents classes a compilar). Deixem la resta de paràmetres per defecte i premem el botó de Finalitzar.
Podem comprovar que se'ns ha creat dos fitxers amb extensions .m i .h que representen a la classe Fraccio. Aquests dos fitxers ja contenen la base per que al compilar representin una classe, veiem que la classe hereta per defecte l'NSObject.
//
// Fraccio.h
// AppFraccio
//
// Created by Xin Xic on 20/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface Fraccio : NSObject {
}
@end
Del codi anterior es pot observar diferents elements que descriurem en aquest paràgraf. En primer lloc veiem uns comentaris amb el nom de la classe a quina aplicació pertany, qui l'ha creat i quan i alguns drets que poden haver-hi. Passant al codi, podem veure la primera directiva de pre-processador on es carrega el fitxer de capçalera de Cocoa. La següent línia pertany a les Directives del Compilador que s'utilitza per declarar una classe nova, aquestes han d'acabar amb @end. En aquest cas la classe Fracció que hereta de la classe base NSObject de la framework Foundation. L'espai entre les claus, està destinat a definir les variables d'instància de la classe i fora de les claus i abans de la clausula @end la definició dels missatges que atendrà la classe.
Per definir la classe es comença definir els missatges que ha d'atendre aquesta classe. En el nostre cas, la classe Fraccio atendrà missatges per definir el numerador i el denominador i també per obtindre'n els valors. (Encara que us pugui semblar estrany que es defineixin primer els missatges abans que les dades, té una lògica que està explicada en el ;anual de Programació Orientada a Objectes i posteriorment en la Guia de Patrons de Disseny amb Cocoa). Així doncs, comencem a definir els missatges que volem que atengui la nostra classe, afegint les següents línies.
... - print; -(void) setNumerador: (int) n; -(void) setDenominador: (int) d; -(int) numerador; -(int) denominador; ...
Aquestes funcions ens serviran per treure imprimir per la sortida d'error un cadena que representi la fracció. També tindrem dues funcions per establir el numerador i el denominador. I per acabar dues funcions que ens retornaran el numerador i el denominador. Recordeu que per defecte els mètodes dins Objective-C retornen un tipus (id) així en els nostres casos hem de definir totes els tipus de retorn menys el print que retorna (id). El símbol "-" d'abans de cada mètode s'utilitza per diferenciar els mètodes d'instància (-) dels mètodes de classe (+), això es veurà més endavant. Altres temes a considerar són el nivell d'accés dels mètodes (protected, public, private), en el nostre cas no s'ha definit i per tant que per defecte (protected) que ja explicarem més endavant que vol dir cada nivell d'accés.
De la definició dels mètodes podem extrapolar les variables d'instància necessàries per pode utilitzar aquests mètodes. Així amb dues variables enteres que representin el numerador i el denominador n'hi hauria prou. Aquesta definició es realitzarà entre les claus de la definició de la classe.
...
@interface Fraccio : NSObject {
int numerador;
int denominador;
}
...
Ara ja tenim la classe definida, el següent pas serà implementar-ne la funcionalitat. Per això agafarem el fitxer amb extensió .m i comencem a afegir la implementació dels mètodes de la nostra classe.
//
// Fraccio.m
// AppFraccio
//
// Created by Xin Xic on 20/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "Fraccio.h"
@implementation Fraccio
- print {
return [NSString stringWithFormat: @"%d/%d", numerador, denominador];
}
-(void) setNumerador: (int) n {
numerador = n;
}
-(void) setDenominador: (int) d {
denominador = d;
}
-(int) numerador {
return numerador;
}
-(int) denominador {
return denominador;
}
@end
No cal explicar massa cosa d'aquest codi. És evident el que fa. Potser val la pena esmentar la directiva @implementation que indica l'inicia de la implementació de la classe Fraccio i finalitza amb la directiva @end. També es interesant fer esment que a l'Objective-C per definir una cadena de caràcters NSString és realitza utilitzant la directiva @ seguida del text entre cometes.
Bé doncs, ara només cal comprovar que el codi de la nostra classe funciona correctament. Per això afegirem el codi main de l'apliació en que farem unes quantes operacions per comprovar-ne el seu funcionament.
#import <Foundation/Foundation.h>
#import "Fraccio.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac = [[Fraccio alloc] init];
// Establim els valors
[frac setNumerador: 1];
[frac setDenominador: 3];
// Ho imprimim
NSLog(@"La fracció és: %@\n", [frac print]);
// Alliberem la memòria
[frac release];
[pool release];
return 0;
}
En el codi de l'aplicació creem una instància de la classe Fracció, li assignem el numerador i el denominador i després n'imprimim el resultat per comprovar que a funcionat. Per acabar alliberem de la memòria la instància de la classe Fraccio que em realitzat al principi. Penseu que amb Objective-C el qui crea una instància és l'encarregat d'alliberar-la de la memòria. També veiem la classe NSAutoreleasePool que pot fer aquestes funcions automàticament, però això ja ho explicarem més endavant.
Analitzant més a fons el codi afegit, podem observar com es crea una instància a Objective-C i com es criden els mètodes. En la creació de la instància el primer que es fa és cridar la funció de classe alloc heretada de NSObject que s'encarrega de reservar la memòria necessària, inicialitzada a zero, per la instància que volem crear i en retorna un (id) de l'objecte. El següent pas és inicialitzar-ne els valors tant de la pròpia classe com de la seva super-classe mitjançant el missatge init.
Un cop compilats i executat el codi, el resultat és el següent:
[Session started at 2005-08-22 10:40:24 +0200.] 2005-08-22 10:40:24.496 AppFraccio[697] La fracció és: 1/3 Executable “AppFraccioâ€? has exited with status 0.
En aquest apartat, entrarem a treballar amb altres conceptes dins de les classes que ens acabaran de donar forma al treball bàsic amb les classes.
Fins aquest punt, no s'ha mostrat cap forma específica per definir paràmetres múltiples. En principi no és intuïtiu, ja que prové de la sintaxi de l'Smalltalk. Per definir múltiples paràmetres en un mètode ho podeu veure a l'afegir al codi de la classe Fraccio un mètode per introduir el numerador i el denominador en un únic mètode.
... -(void) setNumerador: (int) n iDenominador: (int) d; ...
...
-(void) setNumerador: (int) n iDenominador: (int) d {
numerador = n;
denominador = d;
}
...
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac = [[Fraccio alloc] init];
Fraccio *frac2 = [[Fraccio alloc] init];
// Establim els valors
[frac setNumerador: 1];
[frac setDenominador: 3];
[frac2 setNumerador: 1 iDenominador: 5];
// Ho imprimim
NSLog(@"La fracció és: %@\n", [frac print]);
NSLog(@"La fracció 2 és: %@\n", [frac2 print]);
// Alliberem la memòria
[frac release];
[frac2 release];
[pool release];
return 0;
}
Comprovant el funcionament del nou codi ens retorna aquesta soritda:
[Session started at 2005-08-22 11:04:00 +0200.] 2005-08-22 11:04:00.536 AppFraccio[719] La fracció és: 1/3 2005-08-22 11:04:00.538 AppFraccio[719] La fracció 2 és: 1/5 Executable “AppFraccioâ€? has exited with status 0.
Fixant-nos amb el codi afegit, veiem que per a cada nou paràmetre s'han d'afegir dos punts, on opcionalment es pot indicar una descripció que formaria part del nom del mètode. Així un mètode amb 4 paràmetres pot definir-se amb un descripció com aquest metode:etiqueta1:etiqueta2:etiqueta3: que es cridaria així [objecte metode: param1 etiqueta1: param2 etiqueta2: param3 etiqueta3: param4]; o també seria vàlid amb aquest codi metodes:::: i que es cridaria com [objecte metode: param1 : param2 : param3 : param4];. Aquesta possibilitat d'indicar una etiqueta a cada paràmetre ofereix al programador molta informació que potser no tindria sense ells. També és una forma de fer sobrecàrrega de paràmetres, que no està permès dins Objective-C i que afegint-hi les etiquetes es produeix un canvi en el nom de mètode que en faria un sistema semblant a la sobrecàrrega de paràmetres.
El constructor a Objective-C estan implementats sobre les funcions que comencen amb init, com el seu nom indica inicialitzen un objecte i sempre han de retornar un (id) de propi objecte. Anem a implementar-ho en la nostra classe d'exemple Fraccio per veure'n el seu funcionament.
-(Fraccio*) initAmbNumerador: (int) n iDenominador: (int) d;
-(Fraccio*) initAmbNumerador: (int) n iDenominador: (int) d {
self = [super init];
if (self) {
[self setNumerador: n iDenominador: d];
}
return self;
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac = [[Fraccio alloc] init];
Fraccio *frac2 = [[Fraccio alloc] init];
Fraccio *frac3 = [[Fraccio alloc] initAmbNumerador: 3 iDenominador: 10];
// Establim els valors
[frac setNumerador: 1];
[frac setDenominador: 3];
[frac2 setNumerador: 1 iDenominador: 5];
// Ho imprimim
NSLog(@"La fracció és: %@\n", [frac print]);
NSLog(@"La fracció 2 és: %@\n", [frac2 print]);
NSLog(@"La fracció 3 és: %@\n", [frac3 print]);
// Alliberem la memòria
[frac release];
[frac2 release];
[frac3 release];
[pool release];
return 0;
}
En el nou codi podem veure alguns conceptes nous, com per exemple la paraula super. Aquesta paraula clau és semblant a Java doncs només té una única classe pare. Accedir al constructor pare es realitza a través de [super init] i això és necessari fer-ho per una herència adequada. Aquesta retorna una instància que has d'assignar a una altra paraula clau, self. Self és semblant a Java i a C++.
Tingueu en compte que si self és nil el constructor pare ha retornat un error. I és una forma d'assegurar-nos que ha funcionat. Si ha funcionat hem d'inicialitzar les variables de la nostra instància i retornar-nos a nosaltres mateixos. El constructor per defecte és -(id) init; que ja proporciona la classe base NSObject i que es pot heretar.
Un cop compilat i executat ens retorna el següent resultat:
[Session started at 2005-08-22 11:29:35 +0200.] 2005-08-22 11:29:36.017 AppFraccio[825] La fracció és: 1/3 2005-08-22 11:29:36.018 AppFraccio[825] La fracció 2 és: 1/5 2005-08-22 11:29:36.018 AppFraccio[825] La fracció 3 és: 3/10 Executable “AppFraccioâ€? has exited with status 0.
Com hem comentat anteriorment l'accés per defecte és @protected. Així com Java implementa això amb modificadors public/private/protected davant dels mètodes i les variables, l'Objective-C s'assembla molt més al C++. Veurem el funcionament dels privilegis d'accés en un nou projecte d'exemple anomenat AppAcces, el qual tindrà el següent codi dins de la classe Acces i en el mètode main.
Acces.h
#import@interface Acces : NSObject { @public int publicVar; @private int privateVar; @protected int protectedVar; } @end
Acces.m
#import "Acces.h" @implementation Acces @end
AppAcces.m
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem la instància
Acces *a = [[Acces alloc] init];
// treballem-hi
a->publicVar = 5;
NSLog(@"variable pública: %d\n", a->publicVar);
// no compila
a->privateVar = 10;
NSLog(@"variable privada: %d\n", a->privateVar);
// no compila
a->protectedVar = 15;
NSLog(@"variable protegida: %d\n", a->protectedVar);
[a release];
[pool release];
return 0;
}
Les línies amb les variables privades i protegides no retornen error al compilar-se, però adverteixen que en un futur no s'hi podrà accedir i donarà error. D'altra banda, treballar amb punters no és la forma que s'ha de fer amb Objective-C, en teoria no es bona pràctica accedir a les variables d'una classe directament, sempre s'ha de fer utilitzant funcions, doncs com hem dit anteriorment el que defineix una classe no són els seus paràmetres sinó els missatge que pot respondre, i en un moment un classe pot tenir una variable i en un futur desaparèixer al estar implícita en una altra, i això comportaria un error en el codi. Si s'utilitzen funciona per retornar valors, sempre es pot calcular el valor buscat de qualsevol lloc, no cal tenir la variable. Tot això s'explicarà més endavant.
Resumint, els privilegis d'accés de les variables indica la visibilitat respecte altres classes, així una variable publica hi pot accedir tothom, una de protegida totes les classes que hi hereten i les privades solament la pròpia classe. Però amb el comentat abans, s'ha de mirar d'evitar accedir directament a variables d'una altra classe, sempre és millor fer-ho mitjançant missatges de petició.
En punts anteriors hem comentat per sobre el símbol (-) que hi ha davant dels mètodes de les classes, i hem introduït la possibilitat de canviar aquest símbol per un (+). Doncs bé, aquesta característica de l'Objective-C és la forma com aquest permet crear funcions estàtiques, que utilitza el C++. Però l'Objective-C no treballa exactament com el C++. A l'Objective-C quan es defineix una classe i s'utilitza paral·lelament crea una instància d'objecte Class la qual conté tota la informació de la classe, les variables d'instància i les funcions, però també té les variablesdeclarades estàtiques i les funcions de classe definides amb el símbol (+), essent com un tipus de classe més, encara que sigui única. En el següent exemple podem veure com funcina.
ClasseA.h
//
// ClasseA.h
// AppClasse
//
// Created by Xin Xic on 23/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
static int comptador;
@interface ClasseA : NSObject {
}
+(int) comptador;
+(void) initialize;
@end
ClasseA.m
//
// ClasseA.m
// AppClasse
//
// Created by Xin Xic on 23/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "ClasseA.h"
@implementation ClasseA
-(id) init {
self = [super init];
comptador++;
return self;
}
+(int) comptador {
return comptador;
}
+(void) initialize {
comptador = 0;
}
@end
AppClasse.m
#import <Foundation/Foundation.h>
#import "ClasseA.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem les instàncies
ClasseA *c1 = [[ClasseA alloc] init];
ClasseA *c2 = [[ClasseA alloc] init];
// Imprimim el comptador
NSLog(@"Comptador ClasseA: %d\n", [ClasseA comptador]);
// Inicialitzem una altra instància
ClasseA *c3 = [[ClasseA alloc] init];
// Imprimim altra vegada el comptador
NSLog(@"Comptador ClasseA: %d\n", [ClasseA comptador]);
// Alliberem les classes de la memòria
[c1 release];
[c2 release];
[c3 release];
[pool release];
return 0;
}
Anem a analitzar el codi que hem escrit, començant per les declaracions de la implementació de la ClasseA. El primer que cal esmentar és la funció de Classe +(int)initialize; que ve heretada de la classe NSObject i s'executa el primer cop que s'executa una aplicació per a cada tipus de classe que es pugui instanciar dins l'apliació. Com ja hem comentat a part de les instàncies pròpies de les classe cada classe és una instància de tipus Class que també té un "constructor" que pot treballar amb les variables estàtiques. Seguint amb la ClasseA també hem creat una funció de classe que retorna el valor del comptador +(int) comptador;. L'altra funció afegida és el constructor de la classe que incrementa el comptador d'instàncies de la ClasseA. Cal notar que potser caldria restar el comptador qual s'alliberés la instància, però per comprovar el funcionament de les funcions i variables estàtiques n'hi ha prou.
La funció principal de la aplicació crea en un primer moment dues instàncies de la ClasseA i en mostra el comptador d'instàncies i després en crea una altra i en torna a mostrar el comptador per comprovar que aquest funciona correctament. Al final s'alliberen les instàncies de la memòria.
El resultat de la compilació i execució és el següent:
[Session started at 2005-08-23 10:22:12 +0200.] 2005-08-23 10:22:12.613 AppClasse[751] Comptador ClasseA: 2 2005-08-23 10:22:12.613 AppClasse[751] Comptador ClasseA: 3 Executable “AppClasseâ€? has exited with status 0.
El llenguatge Objective-C té una sintaxi de captura d'exepcions similar a la que té Java i C++ amb l'us de les classes NSException, NSError, o de pròpies, pots afegir una robusta captura d'excepcions en els teus programes. El suport d'excepcions giren al voltant de quatre directives del compilador: @try, @catch, @throw i @finally. El codi que potencialment pot llençar una exceptió està tancat per un block @try. Un block @finally conté el codi que ha d'executar-se si un excepció és llença o no. Utilitza la directiva @throw per llançar una excepció, que és essencialment un punter a un objecte Objective-C. Pots utilitzar els objectes NSException però no estan limitades a ells. Podeu veure-ho més extensament aquí
Per veure una mica com funcionen les excepcions anem a escriure un exemple i explicar-lo. Per fer això crearem un projecte nou anomenat AppCopa on hi haurà una classe normal on hi haurà excepcions i un parell de classe d'excepció de diferent tipus per comprovar-ne el funcionament.
CopaUnderflowException.h
//
// CopaUnderflowException.h
// AppCopa
//
// Created by Xin Xic on 23/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface CopaUnderflowException : NSException {
}
@end
CopaUnderflowException.m
// // CopaUnderflowException.m // AppCopa // // Created by Xin Xic on 23/08/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // #import "CopaUnderflowException.h" @implementation CopaUnderflowException @end
CopaWarningException.h
//
// CopaWarningException.h
// AppCopa
//
// Created by Xin Xic on 23/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface CopaWarningException : NSException {
}
@end
CopaWarningException.m
// // CopaWarningException.m // AppCopa // // Created by Xin Xic on 23/08/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // #import "CopaWarningException.h" @implementation CopaWarningException @end
CopaOverflowException.h
//
// CopaOverflowException.h
// AppCopa
//
// Created by Xin Xic on 23/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface CopaOverflowException : NSException {
}
@end
CopaOverflowException.m
// // CopaOverflowException.m // AppCopa // // Created by Xin Xic on 23/08/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // #import "CopaOverflowException.h" @implementation CopaOverflowException @end
Copa.h
//
// Copa.h
// AppCopa
//
// Created by Xin Xic on 23/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface Copa : NSObject {
int nivell;
}
-(int) nivell;
-(void) setNivell: (int) n;
-(void) omple;
-(void) buida;
-(id) print;
@end
Copa.m
//
// Copa.m
// AppCopa
//
// Created by Xin Xic on 23/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "Copa.h"
#import "CopaOverflowException.h"
#import "CopaWarningException.h"
@implementation Copa
-(id) init {
self = [super init];
if ( self ) {
[self setNivell: 0];
}
return self;
}
-(int) nivell {
return nivell;
}
-(void) setNivell: (int) n {
nivell = n;
if ( nivell > 100 ) {
// throw overflow
NSException *e = [CopaOverflowException
exceptionWithName: @"CopaOverflowException"
reason: @"El nivell està per sobre de 100"
userInfo: nil];
@throw e;
} else if ( nivell >= 50 ) {
// throw warning
NSException *e = [CopaWarningException
exceptionWithName: @"CopaWarningException"
reason: @"El nivell està per sobre de 50"
userInfo: nil];
@throw e;
} else if ( nivell < 0 ) {
// throw exception
NSException *e = [NSException
exceptionWithName: @"CopaUnderflowException"
reason: @"El nivell està per sota de 0"
userInfo: nil];
@throw e;
}
}
-(void) omple {
[self setNivell: nivell + 10];
}
-(void) buida {
[self setNivell: nivell - 10];
}
-(id) print {
return [NSString stringWithFormat: @"El nivell de la Copa és: %d\n", nivell ];
}
@end
AppCopa.m
#import <Foundation/Foundation.h>
#import "Copa.h"
#import "CopaUnderflowException.h"
#import "CopaWarningException.h"
#import "CopaOverflowException.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància de la copa.
Copa *copa = [[Copa alloc] init];
int i;
// Això és el que farà
for ( i = 0; i < 4; i++ ) {
[copa omple];
NSLog([copa print]);
}
// Això capturarà les excepcions
NSString *excep;
for ( i = 0; i < 7; i++ ) {
@try {
[copa omple];
} @catch ( CopaUnderflowException *e ) {
excep = [e name];
} @catch ( CopaWarningException *e ) {
excep = [e name];
} @catch ( CopaOverflowException *e ) {
excep = [e name];
} @finally {
NSLog(@"%@: %@", excep, [copa print]);
}
}
// Llença un excepció genèrica
@try {
[copa setNivell: -1];
} @catch ( NSException *e ) {
NSLog( @"%@: %@\n", [e name], [e reason] );
}
// alliberem memòria
[copa release];
[pool release];
return 0;
}
Primer de tot cal destacar que les classes d'excepcions hereten totes elles de NSException, és obligatori si volem que sigui una excepció. L'altre cosa a destacar és que no tenen cap codi, doncs en el nostre cas no els hi volem donar més funcionalitat de la que té NSException i només ens interessa diferenciar els diferents tipus d'excepcions. Un altre punt a destacar és el fet que nosaltres em d'encarregar-nos de decidir on es capturen les excepcions, sinó ho farà al final l'aplicació i potser no en podrem controlar el resultat (possiblement ens tanqui l'apliació), per capturar una excepció s'ha de tancar entre un @try el codi del que volem capturar excepcions, i col·locar un @catch per cada actuació diferents depenent del tipus d'excepció. En el nostre cas podríem realitzar coses diferents per a cada tipus d'excepció, i donat que per cada captura d'excepció és fa el mateix, no caldria separar-les i capturar-les totes com a una excepció NSException. La sentència @finally s'utilitza per indicar el codi que ha d'executar sempre, hi hagi excepció o no. Un altre opció a comentar per fet de treballar amb excepcions és el fet de llançar-les. No serveix de res capturar un excepció si ningú en llança mai ca. En el codi de la copa, llança excepcions mitjançant la comanda @throw quan canvia el nivell de la copa, i depenent del nivell envia un error o un altre. Així es pot considerar que un nivell negatiu seria inviable i el programador agrairà conèixer aquesta incongruència quan s'executa el codi, També hi ha una advertència quan el nivell està per sobre del 50% o quan es supera el nivell de la copa. Després és cosa del programador gestionar aquestes excepcions i decidir que fer-hi.
Passant a explicar el que fa el programa, primer parlem de la classe Copa. Aquesta s'encarrega de variar el seu nivell omplint-lo i buidant-lo de deu en deu, i un cop analitzat el nivell llença les excepcions adients a l'estat causat.
El codi de l'aplicació crea una copa que va omplint de mica en mica. Primer incrementa el seu nivell 4 vegades fins al 40% i no es preocupa de capturar cap impressió (si augmenteu el nombre de cops de s'incrementa el nivell de la copa sense capturar la excepció veureu que com que no es tracta l'excepció l'aplicació finalitza el programa)
2005-08-23 13:10:25.446 AppCopa[1500] *** Uncaught exception:El nivell està per sobre de 50 Executable “AppCopaâ€? has exited due to signal 5 (SIGTRAP).
Però be, continuem amb el nostre programa. Un cop omplert fins al 40% es torna a repetir l'operació fins al 110% però aquest cop mirant de capturar les possibles excepcions que es produiran. Per acabar es deixa el nivell de la copa per sota de zero i també es captura l'excepció, en aquest cas sense diferenciar el codi d'aquesta excepció, doncs només se'n vol imprimir el nom i la raó que l'ha causat. Executant normalment l'aplicació retorna la següent sortida:
[Session started at 2005-08-23 13:14:24 +0200.] 2005-08-23 13:14:24.495 AppCopa[1512] El nivell de la Copa és: 10 2005-08-23 13:14:24.523 AppCopa[1512] El nivell de la Copa és: 20 2005-08-23 13:14:24.540 AppCopa[1512] El nivell de la Copa és: 30 2005-08-23 13:14:24.557 AppCopa[1512] El nivell de la Copa és: 40 2005-08-23 13:14:24.587 AppCopa[1512] CopaWarningException: El nivell de la Copa és: 50 2005-08-23 13:14:24.605 AppCopa[1512] CopaWarningException: El nivell de la Copa és: 60 2005-08-23 13:14:24.621 AppCopa[1512] CopaWarningException: El nivell de la Copa és: 70 2005-08-23 13:14:24.637 AppCopa[1512] CopaWarningException: El nivell de la Copa és: 80 2005-08-23 13:14:24.652 AppCopa[1512] CopaWarningException: El nivell de la Copa és: 90 2005-08-23 13:14:24.672 AppCopa[1512] CopaWarningException: El nivell de la Copa és: 100 2005-08-23 13:14:24.688 AppCopa[1512] CopaOverflowException: El nivell de la Copa és: 110 2005-08-23 13:14:24.688 AppCopa[1512] CopaUnderflowException: El nivell està per sota de 0 Executable “AppCopaâ€? has exited with status 0.
Tingueu en compte que s'ha hagut d'habilitar l'opció d'excepcions amb Objective-C del compilador, que no la tenia per defecte.
En aquest apartat començarem a ficar-nos dins la Programació Orientada a Objectes (POO) que ens ofereix l'Objective-C. En aquest enllaç es parla sobre la POO des d'una visió de l'Objective-C i Cocoa.
Per començar parlarem del tipus id que actua de forma similar al tipus void* de C, encara que aquest solaament per referenciar objectes. L'Objective-C es diferencia del Java i del C++ en que quan crides a un mètode d'un objecte, aquest no necessita conèixer el tipus de l'objecte. Aquest mètode només cal que existeixi. A això a l'Objective-C se'n diu passar un missatge. Anem a fer un exemple per veure com funciona.
En aquest primer exemple utilitzarem el codi que ja teniem de l'Applicació AppFraccio, i per veure una altre sistema de crear diferents executables amb XCode a partir del mateix codi, crearem un nou "target" per estalviar-nos modificar l'aplicació anterior i/o copiar algunes classes, com poden ser la classe Fraccio.
Per crear un executable alternatiu seguirem els següents passos. Primer de tot obrim el projecte base anterior AppFracio. Anem a la opció dels menus "Project > New Target", d'on ens sortirà un assistent que ens permetrà escollir quin tipus d'executamble volem crear. En el nostre cas sel·leccionarem "Cocoa > Shell Tool" i li ficarem com a nom de l'aplicació AppComplex (doncs també haurem de crear aquesta classe). Ara ens apareix a l'arbre de navegació de l'XCode un nou target anomenat AppComplex.
Ara cal afegir quins arxius es compilaran en aquest nou executable. Això és tant senzill com desplaçar els fitxers que volem a dins de "Targets > AppComplex > Sources". També ens caldrà afegir les frameworks necessàries per que compili correctament, doncs fem-ho directament de la framework del codi i la enviem amb el ratolí cap a "Targets > AppComplex > Frameworks & Libraries". Podeu crear més "Fases de Construcció" des del menú contextual "Add > New Build Phase" i sel·leccionar el que necessitis. També ho pots fer des del menú "Project" principal. Si us hi fixeu, un dels targets té un símbol verd d'acceptat que indica quin és el target actual. Podem canviar-ho i fer que AppComplex sigui l'actual, només cal anar a l'opció "Active Target" de la barra d'eines i seleccionar AppComplex.
Doncs ara només cal afegir els fitxers i classes noves dins d'aquest nou target. La primera d'aquestes serà la classe Complex que representarà a un número complexe (real, imaginari) i que l'assignarem al nou target. Si us hi fixeu quan creem una nova classe ens demana a quins targets es compilarà, podeu veure que el fitxer complex.m s'ha afegit a "Targets > AppComplex > Sources". I ja està, ara només cal escriure el codi.
Complex.h
//
// Complex.h
// AppFraccio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface Complex : NSObject {
double real;
double imaginari;
}
// Init's
-(Complex*) initAmbReal: (double) r iImaginari: (double) i;
// Set's
-(void) setReal: (double) r;
-(void) setImaginari: (double) i;
-(void) setReal: (double) r iImaginari: (double) i;
// Get's
-(double) real;
-(double) imaginari;
// Altres Funcions
-(id) print;
@end
Complex.m
//
// Complex.m
// AppFraccio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "Complex.h"
@implementation Complex
// Init's
-(Complex*) initAmbReal: (double) r iImaginari: (double) i {
self = [super init];
if (self) {
[self setReal: r iImaginari: i];
}
return self;
}
// Set's
-(void) setReal: (double) r {
real = r;
}
-(void) setImaginari: (double) i {
imaginari = i;
}
-(void) setReal: (double) r iImaginari: (double) i {
real = r;
imaginari = i;
}
// Get's
-(double) real {
return real;
}
-(double) imaginari {
return imaginari;
}
// Altres funciona
-(id) print {
return [NSString stringWithFormat: @"( %f + %fi )", real, imaginari];
}
@end
I per acabar hem de crear el fitxer amb la funció main que s'encarregarà de realitzar la tasca del nostre target. Aquest l'anomenarem AppComplex.m i crearà una instància de Fracció i una altra de Complex, i a partir d'una variable (id) que li assignarem cadascun dels dos objectes i enviarem el missatge print comprovant que no hi ha cap problema de compilació i que a l'hora d'executar-se crida a la funció esperada (això mateix no funcionaria amb C++ ni amb Java).
AppComplex.m
#import <Foundation/Foundation.h>
#import "Fraccio.h"
#import "Complex.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac = [[Fraccio alloc] initAmbNumerador: 1 iDenominador: 10];
Complex *comp = [[Complex alloc] initAmbReal: 10 iImaginari: 15];
id nombre;
// Imprimim la fracció
nombre = frac;
NSLog(@"La fracció és: %@\n", [nombre print]);
// Imprimim el nombre complex
nombre = comp;
NSLog(@"El nombre complex és: %@\n", [nombre print]);
// Alliberem la memòria
[frac release];
[comp release];
[pool release];
return 0;
}
Un cop compilat i executat aquest codi, ens retorna la següent sortida, comprovant que l'enviament de missatges a Objective-C no es comprova a l'hora de la compilació sinó en el moment de l'execució.
[Session started at 2005-08-27 12:33:25 +0200.] 2005-08-27 12:33:26.193 AppComplex[788] La fracció és: 1/10 2005-08-27 12:33:26.194 AppComplex[788] El nombre complex és: ( 10.000000 + 15.000000i ) Executable “AppComplexâ€? has exited with status 0.
També podeu comprovar l'error que es produeix i no existeix una de les funcions canviat el nom de la funció "print" de la classe Complex a "imprimir" i compileu-lo (sense cap problema) i executeu-lo (donant un error)
[Session started at 2005-08-27 12:42:52 +0200.] 2005-08-27 12:42:52.070 AppComplex[802] La fracció és: 1/10 2005-08-27 12:42:52.071 AppComplex[802] *** -[Complex print]: selector not recognized 2005-08-27 12:42:52.072 AppComplex[802] *** Uncaught exception:*** -[Complex print]: selector not recognized Executable “AppComplexâ€? has exited due to signal 5 (SIGTRAP).
Hi ha beneficis obvis en aquest tipus d'enllaçat dinàmic. No tens que conèixer el tipus d'un objecte per enviar-li un missatge (cridar un mètode). Si l'objecte respon al missatge, aquest invocarà aquest mètode.
L'herència és un dels mecanismes estrella de la programació orientada a objectes. Permet aprofitar codi estenent-ne la funcionalitat mitjançant l'herència. Tota la teoria sobre l'herència i quan utilitzar-la ho podeu trobar més extensament aquí.
L'herència a Objective-C és semblant a Java. Quan extens la teva super-classe (de tal manera que només tens un sol pare) pots sobre-escriure els mètodes de la teva super-classe simplement posant noves implementacions en la implementació de la classe filla. No ho confongueu amb les funcions virtuals de C++.
Per comprovar com funciona la herència realitzarem un nou projecte amb un parell de classes que representen un rectangle i un quadrat. Per això crearem un nou projecte que anomenarem AppHerencia.
Rectangle.h
//
// Rectangle.h
// AppHerencia
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface Rectangle : NSObject {
int amplada;
int alsada;
}
// Init's
-(Rectangle*) initAmbAmplada: (int) w iAlsada: (int) h;
// Set's
-(void) setAmplada: (int) w;
-(void) setAlsada: (int) h;
-(void) setAmplada: (int) w iAlsada: (int) h;
// Get's
-(int) amplada;
-(int) alsada;
// Altres funcions
-(id) print;
@end
Rectangle.m
//
// Rectangle.m
// AppHerencia
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "Rectangle.h"
@implementation Rectangle
// Init's
-(Rectangle*) initAmbAmplada: (int) w iAlsada: (int) h {
self = [super init];
if ( self ) {
[self setAmplada: w iAlsada: h];
}
return self;
}
// Set's
-(void) setAmplada: (int) w {
amplada = w;
}
-(void) setAlsada: (int) h {
alsada = h;
}
-(void) setAmplada: (int) w iAlsada: (int) h {
amplada = w;
alsada = h;
}
// Get's
-(int) amplada {
return amplada;
}
-(int) alsada {
return alsada;
}
// Altres funciona
-(id) print {
return [NSString stringWithFormat:@"Amplada: %d, Alçada: %d", amplada, alsada];
}
@end
Quadrat.h
//
// Quadrat.h
// AppHerencia
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "Rectangle.h"
@interface Quadrat : Rectangle {
}
// Init's
-(Quadrat*) initAmbDimensio: (int) d;
// Set's
-(void) setDimensio: (int) d;
// Get's
-(int) dimensio;
@end
Quadrat.m
//
// Quadrat.m
// AppHerencia
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "Quadrat.h"
@implementation Quadrat
// Init's
-(Quadrat*) initAmbDimensio: (int) d {
self = [super init];
if (self ) {
[self setDimensio: d];
}
return self;
}
// Set's
-(void) setDimensio: (int) d {
amplada = d;
alsada = d;
}
-(void) setAmplada: (int) w {
[self setDimensio: w];
}
-(void) setAlsada: (int) h {
[self setDimensio: h];
}
// Get's
-(int) dimensio {
return amplada;
}
@end
AppHerencia.m
#import <Foundation/Foundation.h>
#import "Rectangle.h"
#import "Quadrat.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem les instàncies
Rectangle *rec = [[Rectangle alloc] initAmbAmplada: 10 iAlsada: 20];
Quadrat *qua = [[Quadrat alloc] initAmbDimensio: 15];
// Els imprimim
NSLog(@"Rectangle: %@\n", [rec print]);
NSLog(@"Quadrat: %@\n", [qua print]);
// Actualitzem el quadrat
[qua setDimensio: 20];
NSLog(@"Quadrat després del canvi: %@\n", [qua print]);
// Alliberem la memòria
[rec release];
[qua release];
[pool release];
return 0;
}
Un compilada i executada l'aplicació el resultat obtingut serà el següent:
[Session started at 2005-08-27 14:41:17 +0200.] 2005-08-27 14:41:17.330 AppHerencia[941] Rectangle: Amplada: 10, Alçada: 20 2005-08-27 14:41:17.332 AppHerencia[941] Quadrat: Amplada: 15, Alçada: 15 2005-08-27 14:41:17.332 AppHerencia[941] Quadrat després del canvi: Amplada: 20, Alçada: 20 Executable “AppHerenciaâ€? has exited with status 0.
Que passaria si intentes cridar el constructor del rectangle utilitzant la classe Quadrat Quadrat *qua = [[Quadrat alloc] initAmbAmplada: 10 iAlsada: 15]. La resposta és que compilador donaria error de compilació. Ja que el tipus retornat pel constructor és Rectangle*, no Quadrat* això no podria funcionar. En cas que et passi això, l'ús de la variable (id) és millor. Només canvia el tipus de retorn Rectangle* per id si vols utilitzar els constructors pare en una sub-classe.
Hi ha diverses formes de treballar amb tipus dinàmics a Objective-C
-(BOOL) isKindOfClass: classObj: retorna YES si un objecte hereta d'una classe "classObj" en particular.-(BOOL) isMemberOfClass: classObj: retorna YES si un objecte és pertany a una classe "classObj".-(BOOL) responsdsToSelector: selector: retorna YES si l'objecte te un mètode anomenat com el "selector"+(BOOL) instancesRespondToSelector: selector: retorna YES si l'objecte creat per aquesta classe té l'habilitat de respondre a un selector específic.-(id) performSelector: selector: invoca el selector específic de l'objecte.Cada objecte heretat de NSObject té un mètode de classe que retorna l'objecte de la classe. Aquest és molt semblant al mètode getClass() de Java. Aquesta objecte classe s'utilitza en els mètodes anteriors. Els selector s'utilitzen per representar un missatge dins Objective-C. La sintaxi per crear un selector es mostrarà en el següent exemple, el qual utilitzarà les classes de l'exemple anterior "Rectangle" i "Quadra". Per això crearem un target nou anomenat AppDinamic.
AppDinamic:
El resultat és el següent:
[Session started at 2005-08-27 16:44:11 +0200.] 2005-08-27 16:44:11.842 AppDinamic[1090] el quadrat és un membre de la classe Quadrat 2005-08-27 16:44:11.843 AppDinamic[1090] el quadrat és un Quadrat 2005-08-27 16:44:11.843 AppDinamic[1090] el quadrat és un Rectangle 2005-08-27 16:44:11.843 AppDinamic[1090] el quadrat és un NSObject 2005-08-27 16:44:11.843 AppDinamic[1090] el quadrat respon al mètode setDimensio 2005-08-27 16:44:11.843 AppDinamic[1090] el quadrat respon al mètode alloc 2005-08-27 16:44:11.843 AppDinamic[1090] el quadrat respon al mètode setDimensio Executable “AppDinamicâ€? has exited with status 0.
Podeu comprovar que la instància "qua" no és una instància d'un Rectangle. Que "qua" és un Quadrat, un Rectangle i un NSObject. I també comprovem com funcionen els selectors.
Quan vols afegir mètodes a una classe, normalment n'heretes. Tanmateix aquesta solució no és sempre perfecte, especialment si vols re-escriure la funcionalitat d'una classe de la que no tens el codi font. Les categories et permeten afegir funcionalitat a classes ja existents sense heretar-les. Ruby també té una funcionalitat semblant a aquesta. En el següent exemple se'n pot comprovar el funcionament. Partint de l'exemple de la fracció crearem un nou target AppMatematica on hi afegirem noves funcions mitjançant una categoria.
FraccioMatematica.h
// // FraccioMatematica.h // AppFraccio // // Created by Xin Xic on 27/08/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // #import <Cocoa/Cocoa.h> #import "Fraccio.h" @interface Fraccio (Matematica) // Operacions matemàtiques amb fraccions -(Fraccio*) sum: (Fraccio*) f; -(Fraccio*) mul: (Fraccio*) f; -(Fraccio*) div: (Fraccio*) f; -(Fraccio*) res: (Fraccio*) f; @end
FraccioMatematica.m
//
// FraccioMatematica.m
// AppFraccio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "FraccioMatematica.h"
@implementation Fraccio (Matematica)
// Operacions matemàtiques amb fraccions.
-(Fraccio*) sum: (Fraccio*) f {
return [[Fraccio alloc] initAmbNumerador: numerador * [f denominador] + denominador * [f numerador]
iDenominador: denominador * [f denominador]];
}
-(Fraccio*) mul: (Fraccio*) f {
return [[Fraccio alloc] initAmbNumerador: numerador * [f numerador]
iDenominador: denominador * [f denominador]];
}
-(Fraccio*) div: (Fraccio*) f {
return [[Fraccio alloc] initAmbNumerador: numerador * [f denominador]
iDenominador: denominador * [f numerador]];
}
-(Fraccio*) res: (Fraccio*) f {
return [[Fraccio alloc] initAmbNumerador: numerador * [f denominador] - denominador * [f numerador]
iDenominador: denominador * [f denominador]];
}
@end
AppMatematica.h
#import <Foundation/Foundation.h>
#import "Fraccio.h"
#import "FraccioMatematica.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac1 = [[Fraccio alloc] initAmbNumerador: 1 iDenominador: 3];
Fraccio *frac2 = [[Fraccio alloc] initAmbNumerador: 2 iDenominador: 5];
Fraccio *frac3 = [frac1 mul: frac2];
// Ho Imprimim
NSLog(@"%@ * %@ = %@\n", [frac1 print], [frac2 print], [frac3 print]);
// Alliberem la memòria
[frac1 release];
[frac2 release];
[frac3 release];
[pool release];
return 0;
}
El resultat és el següent:
[Session started at 2005-08-27 19:36:58 +0200.] 2005-08-27 19:36:58.749 AppMatematica[1291] 1/3 * 2/5 = 2/15 Executable “AppMatematicaâ€? has exited with status 0.
Aquí hi ha dues línies màgiques @implementation i @interface: @interface Fraccio (Matematica) i @implementation Fraccio (Matematica). Només hi pot haver una sola categoria amb el mateix nom. Tanmateix, es poden afegir a una classe tantes categories com sigui necessaries, amb noms diferents.
Les categories no poden tenir variables d'instància. S'utilitzen normalment per crear mètodes privats. Com que l'Objective-C no te noció de mètode privats/protegits/publics com fa el Java, s'han de crear categories per amagar la funcionalitat. La forma de fer això és movent els mètodes privats des del fitxer de capçalera de la classe (.h) al fitxer d'implementació (.m) utilitzant una Categoria. Amb el següent exemple ho entendreu:
LaMevaClasse.h
//
// LaMevaClasse.h
// AppCategoriaPresentacio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface LaMevaClasse : NSObject {
}
-(id) metodePublic;
@end
LaMevaClasse.m
//
// LaMevaClasse.m
// AppCategoriaPresentacio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "LaMevaClasse.h"
@implementation LaMevaClasse
-(id) metodePublic {
return @"Mètode Públic";
}
@end
// mètodes privats
@interface LaMevaClasse (Privat)
-(id) metodePrivat;
@end
@implementation LaMevaClasse (Privat)
-(id) metodePrivat {
return @"Mètode Privat";
}
@end
AppCategoria.h
#import <Foundation/Foundation.h>
#import "LaMevaClasse.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem la instància de la classe.
LaMevaClasse *obj = [[LaMevaClasse alloc] init];
// Això funciona
NSLog([obj metodePublic]);
// Això llança un erro quan compila
//NSLog([obj metodePrivat]);
// Alliberem la memòria
[obj release];
[pool release];
return 0;
}
La Presentació és semblant a les categories, però amb una variació. Et permet estendre una classe, i fer que la seva classe es mostri globalment (en lloc de) la super-classe. Per exemple: Posem que tens un NSArrayChils que estén de NSArray. Si fas presentació de NSArrayChils per NSArray tot el teu codi que comenci utilitzarà instàncies de NSArrayChild instanciades de NSArray automàticament. Mirem-ho amb un exemple a partir del projecte Fraccio:
FraccioB.h
// // FraccioB.h // AppFraccio // // Created by Xin Xic on 27/08/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // #import <Cocoa/Cocoa.h> #import "Fraccio.h" @interface FraccioB : Fraccio -(id) print; @end
FraccioB.m
//
// FraccioB.m
// AppFraccio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "FraccioB.h"
@implementation FraccioB
-(id) print {
return [NSString stringWithFormat: @"(%d / %d)", numerador, denominador ];
}
@end
AppFraccioB.h
#import <Foundation/Foundation.h>
#import "Fraccio.h"
#import "FraccioB.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac = [[Fraccio alloc] initAmbNumerador: 3 iDenominador: 10];
// Imprimir-ho
NSLog(@"La fracció és: %@\n", [frac print]);
// Fem que FraccioB sigui la presentació de Fraccio
[FraccioB poseAsClass: [Fraccio class]];
// Creem una nova instància nova
Fraccio *frac2 = [[Fraccio alloc] initAmbNumerador: 3 iDenominador: 10];
// Imprimir-ho
NSLog(@"La fracció és: %@\n", [frac2 print]);
[frac release];
[frac2 release];
[pool release];
return 0;
}
La sortida d'aquest codi és el següent:
[Session started at 2005-08-27 21:04:45 +0200.] 2005-08-27 21:04:46.189 AppFraccioB[1425] La fracció és: 3/10 2005-08-27 21:04:46.191 AppFraccioB[1425] La fracció és: (3 / 10) Executable “AppFraccioBâ€? has exited with status 0.
La sortida del programa s'imprimeix el primer cop com 3/10. El següent cop com (3/10), el qual està implementat a FraccioB. El mètode poseAsClass és part de NSObject. Aquest permet a una sub-classe presentar-se en lloc super-classe.
Realment és sorprenent aquesta funcionalitat. Amb quina facilitat pot canviar-se el funcionament d'una aplicació amb una sola instrucció. Només els avantatges que suposa a l'hora de comprovar alternatives és impressionant.
Un protocol a l'Objective-C és idèntic en funcionalitat que una interfície a Java, o una classe virtual pura a C++. Mirem aquest exemple basat en la classe Fraccio, encara que en farem de nou, per no espatllar els exemples ja realitzats.
Imprimint.h
/* * Imprimint.h * AppProtocol * * Created by Xin Xic on 01/09/05. * Copyright 2005 __MyCompanyName__. All rights reserved. * */ #import <Cocoa/Cocoa.h> @protocol Imprimint -(id) print; @end
Fraccio.h
//
// Fraccio.h
// AppFraccio
//
// Created by Xin Xic on 20/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "Imprimint.h"
@interface Fraccio : NSObject <Imprimint, NSCopying> {
int numerador;
int denominador;
}
// Constructors
-(Fraccio*) initAmbNumerador: (int) n iDenominador: (int) d;
// Set's
-(void) setNumerador: (int) n;
-(void) setDenominador: (int) d;
-(void) setNumerador: (int) n iDenominador: (int) d;
// Get's
-(int) numerador;
-(int) denominador;
@end
Fraccio.m
//
// Fraccio.m
// AppFraccio
//
// Created by Xin Xic on 20/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "Fraccio.h"
@implementation Fraccio
-(Fraccio*) initAmbNumerador: (int) n iDenominador: (int) d {
self = [super init];
if (self) {
[self setNumerador: n iDenominador: d];
}
return self;
}
-(void) setNumerador: (int) n {
numerador = n;
}
-(void) setDenominador: (int) d {
denominador = d;
}
-(void) setNumerador: (int) n iDenominador: (int) d {
numerador = n;
denominador = d;
}
-(int) numerador {
return numerador;
}
-(int) denominador {
return denominador;
}
- print {
return [NSString stringWithFormat: @"%d/%d", numerador, denominador];
}
-(Fraccio*) copyWithZone: (NSZone*) zone {
return [[Fraccio allocWithZone: zone] initAmbNumerador: numerador
iDenominador: denominador];
}
@end
Complex.h
//
// Complex.h
// AppFraccio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "Imprimint.h"
@interface Complex : NSObject <Imprimint> {
double real;
double imaginari;
}
// Init's
-(Complex*) initAmbReal: (double) r iImaginari: (double) i;
// Set's
-(void) setReal: (double) r;
-(void) setImaginari: (double) i;
-(void) setReal: (double) r iImaginari: (double) i;
// Get's
-(double) real;
-(double) imaginari;
@end
Complex.m
//
// Complex.m
// AppFraccio
//
// Created by Xin Xic on 27/08/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "Complex.h"
@implementation Complex
// Init's
-(Complex*) initAmbReal: (double) r iImaginari: (double) i {
self = [super init];
if (self) {
[self setReal: r iImaginari: i];
}
return self;
}
// Set's
-(void) setReal: (double) r {
real = r;
}
-(void) setImaginari: (double) i {
imaginari = i;
}
-(void) setReal: (double) r iImaginari: (double) i {
real = r;
imaginari = i;
}
// Get's
-(double) real {
return real;
}
-(double) imaginari {
return imaginari;
}
// Altres funciona
-(id) print {
return [NSString stringWithFormat: @"( %f + %fi )", real, imaginari];
}
@end
AppProtocol.h
#import <Foundation/Foundation.h>
#import "Fraccio.h"
#import "Complex.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac = [[Fraccio alloc] initAmbNumerador: 3 iDenominador: 10];
Complex *comp = [[Complex alloc] initAmbReal: 5 iImaginari: 15];
id imprimible;
id copiableImprimible;
// Ho imprimim
imprimible = frac;
NSLog(@"La fracció és: %@\n", [imprimible print]);
imprimible = comp;
NSLog(@"El nombre complex és: %@\n", [imprimible print]);
// Això compila perquè la Fraccio conforma tant amb Imprimible com amb NSCopying
copiableImprimible = frac;
// Això no compila perquè el Complex no conforma amb Imprimible
// copiableImprimible = comp;
// Ho comprovem
// cert
if ( [frac conformsToProtocol: @protocol(NSCopying)] == YES )
NSLog(@"La fracció conforma amb NSCopying\n");
// false
if ( [comp conformsToProtocol: @protocol(NSCopying)] == YES )
NSLog(@"La fracció conforma amb NSCopying\n");
// Alliberem la memòria
[frac release];
[comp release];
[pool release];
return 0;
}
Donant com a sortida:
[Session started at 2005-09-01 16:22:19 +0200.] 2005-09-01 16:22:19.870 AppFraccio[1957] La fracció és: 3/10 2005-09-01 16:22:19.871 AppFraccio[1957] El nombre complex és: ( 5.000000 + 15.000000i ) 2005-09-01 16:22:19.871 AppFraccio[1957] La fracció conforma amb NSCopying Executable “AppFraccioâ€? has exited with status 0.
L'especificació del protocol és molt senzilla. És bàsicament @protocol NomDelProtocol (mètodes que s'han d'implementar) @end. Per estar conforme a un protocol, has de posar les protocols que el conformen dins els <>, i separats entre ells per comes. Per exemple: @interface AlgunaClasse <Protocol1, Protocol2, Protocol3>.
Els mètode que el protocol requereix que s'implementin no calen que estiguin en la llista de mètodes del fitxer de capçalera. Com pots veure, Complex.h no té una definició per -(id) print, però segueix implementatge si es vol conformar el protocol. L'únic aspecte del sistema d'interfície de l'Objective-C és especificar el tipus. Més enllà d'especificar com en Java o C++: Imprimint *algunaVariable = (Imprimint*) frac; en Objective-C per exemple, s'utilitza el tipus id amb un protocol restringit: id <Imprimint> var = frac;. Això permet especificar dinàmicament un tipus que requereix múltiples protocols, tots amb una sola variable. Així: id <Imprimint, NSCopying> var = frac;. Com es feia amb el @selector per comprovar l'herència d'un objecte, pots utilitzar @protocol per comprovar la conformancia de les interfícies. [objecte conformsToProtocol: @protocol( AlgunProtocol )] que retornen un BOOL si l'objecte conforma a aquest protocol. Aquest treballa igual per les classes: [AlgunaClasse conformsToProtocol: @protocol( AlgunProtocol )].
Fins ara hem esquivat la gestió de la memòria a l'Objective-C. Segurament pots cridar a una funció dealloc a un objecte, però que succeeix si l'objecte té punters a altres objectes? S'ha de ser conscient que també s'ha d'alliberar la memòria d'aquests objectes. També s'explicarà com la framework Foundation gestiona la memòria quan crea classes.
Fixeu-vos que cada exemple que s'ha fet s'ha gestionar correctament la memòria, sense que t'ho hagis hagut de preguntar.
Els mètodes retain i release són heretats de qualsevol objecte que ha heretat de NSObject. Cada objecte té un comptador intern que pot utilitzar-se per mantenir un registre del nombre de referències que té l'objecte. Així si tens 3 referències, no podràs esborra-lo tu mateix. No obstant un cop s'arribi a 0, ja el podràs esborrar. [objecte retain] incrementa el comptador en una unitat i [objecte release] el decrementa. Si la invocació de [objecte release] causa que el comptador arribi a 0, l'esborrat es produeix.
Mire-m'ho en un exemple:
Fraccio.m
...
-(void) dealloc {
printf( "Esborrant una fraccio\n" );
[super dealloc];
}
...
AppMemoria.m
#import <Foundation/Foundation.h>
#import "Fraccio.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac1 = [[Fraccio alloc] init];
Fraccio *frac2 = [[Fraccio alloc] init];
// Imprimim els comptadors actuals
NSLog(@"Comptador de referències de la fracció 1: %d\n", [frac1 retainCount]);
NSLog(@"Comptador de referències de la fracció 2: %d\n", [frac2 retainCount]);
// els incrementem
[frac1 retain]; // 2
[frac1 retain]; // 3
[frac2 retain]; // 2
// Imprimim els comptadors actuals
NSLog(@"Comptador de referències de la fracció 1: %d\n", [frac1 retainCount]);
NSLog(@"Comptador de referències de la fracció 2: %d\n", [frac2 retainCount]);
// els decrementem
[frac1 release]; // 2
[frac2 release]; // 1
// Imprimim els comptadors actuals
NSLog(@"Comptador de referències de la fracció 1: %d\n", [frac1 retainCount]);
NSLog(@"Comptador de referències de la fracció 2: %d\n", [frac2 retainCount]);
// Alliberem la memòria
[frac1 release];
[frac1 release];
[frac2 release];
[pool release];
return 0;
}
Retornant la següent sortida:
[Session started at 2005-09-01 16:54:52 +0200.] 2005-09-01 16:54:52.102 AppMemoria[1989] Comptador de referències de la fracció 1: 1 2005-09-01 16:54:52.102 AppMemoria[1989] Comptador de referències de la fracció 2: 1 2005-09-01 16:54:52.102 AppMemoria[1989] Comptador de referències de la fracció 1: 3 2005-09-01 16:54:52.102 AppMemoria[1989] Comptador de referències de la fracció 2: 2 2005-09-01 16:54:52.103 AppMemoria[1989] Comptador de referències de la fracció 1: 2 2005-09-01 16:54:52.103 AppMemoria[1989] Comptador de referències de la fracció 2: 1 2005-09-01 16:54:52.103 AppMemoria[1989] Esborrant una fraccio 2005-09-01 16:54:52.103 AppMemoria[1989] Esborrant una fraccio Executable “AppMemoriaâ€? has exited with status 0.
Les crides a retain incrementa el comptador. Les crides a release el decrementa. Es pot obtenir com un enter cridant a [obj retainCount]. Un cop s'arriba a 0, ambdos objectes s'esborren a ells mateixos i pots veure com ambdues ho imprimeixen.
Quan el teu objecte conté altres objectes, has d'alliberar-los quan tu mateix t'eliminis. Una dels bonics avantatges de l'Objective-C és que pots passar missatges a nil, així no hi ha errors de comprovació a l'alliberar un objecte.
Mireu el següent exemple per entendre-ho millor:
TargetaAdresa.h
//
// TargetaAdresa.h
// AppTargetaAdresa
//
// Created by Xin Xic on 01/09/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface TargetaAdresa : NSObject {
NSString *nom;
NSString *cognom;
NSString *correu;
}
// Init's
-(TargetaAdresa*) initAmbNom: (NSString*) n
cognom: (NSString*) c
iCorreu: (NSString*) e;
// Get's
-(NSString*) nom;
-(NSString*) cognom;
-(NSString*) correu;
// Set's
-(void) setNom: (NSString*) n;
-(void) setCognom: (NSString*) c;
-(void) setCorreu: (NSString*) e;
-(void) setNom: (NSString*) n
iCognom: (NSString*) c;
-(void) setNom: (NSString*) n
cognom: (NSString*) c
iCorreu: (NSString*) e;
// Altres
-(id) print;
@end
TargetaAdresa.m
//
// TargetaAdresa.m
// AppTargetaAdresa
//
// Created by Xin Xic on 01/09/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "TargetaAdresa.h"
@implementation TargetaAdresa
// Init's
-(TargetaAdresa*) initAmbNom: (NSString*) n
cognom: (NSString*) c
iCorreu: (NSString*) e {
self = [super init];
if ( self ) {
[self setNom: n cognom: c iCorreu: e];
}
return self;
}
// Get's
-(NSString*) nom {
return nom;
}
-(NSString*) cognom {
return cognom;
}
-(NSString*) correu {
return correu;
}
// Set's
-(void) setNom: (NSString*) n {
[n retain];
[nom release];
nom = n;
}
-(void) setCognom: (NSString*) c {
[c retain];
[cognom release];
cognom = c;
}
-(void) setCorreu: (NSString*) e {
[e retain];
[correu release];
correu = e;
}
-(void) setNom: (NSString*) n
iCognom: (NSString*) c {
[self setNom: n];
[self setCognom: c];
}
-(void) setNom: (NSString*) n
cognom: (NSString*) c
iCorreu: (NSString*) e {
[self setNom: n];
[self setCognom: c];
[self setCorreu: e];
}
// Altres
-(id) print {
return [NSString stringWithFormat: @"%@ %@ <%@>", nom , cognom, correu];
}
// Sobre-escrites
-(void) dealloc {
[nom release];
[cognom release];
[correu release];
[super dealloc];
}
@end
AppTargetaAdresa.h
#import <Foundation/Foundation.h>
#import "TargetaAdresa.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem les dades
NSString *nom = [[NSString alloc] initWithFormat: @"Tom"];
NSString *cognom = [[NSString alloc] initWithFormat: @"Jones"];
NSString *correu = [[NSString alloc] initWithFormat: @"tom@jones.com"];
TargetaAdresa *tom = [[TargetaAdresa alloc] initAmbNom: nom
cognom: cognom
iCorreu: correu];
// ja no necessitem els strings, podem esborrar-los
[nom release];
[cognom release];
[correu release];
// imprimim per comprovar que s'ha retingut
NSLog(@"Contador: %d\n", [[tom nom] retainCount]);
NSLog(@"%@\n", [tom print]);
// Alliberem la memòria
[tom release];
[pool release];
return 0;
}
La sortida d'aquest exemple serà el següent:
[Session started at 2005-09-01 17:23:47 +0200.] 2005-09-01 17:23:48.181 AppTargetaAdresa[2020] Contador: 1 2005-09-01 17:23:48.183 AppTargetaAdresa[2020] Tom JonesExecutable “AppTargetaAdresaâ€? has exited with status 0.
Aquest exemple mostra no només el que fa el mètode dealloc, com es mostra a TargetaAdresa.m, sinó com una forma per ser variables membre. L'ordre de les tres operacions en cada mètode set és molt important. Et permet passar un paràmetre a un dels teus mètodes. Si l'alliberes primer, LLAVORS al fer una retenció et destruiràs tu mateix. Per això sempre has de fer 1) retain 2) release 3) assignar el valor.
Normalment no es poden inicialitzar variables amb strings C perquè no suporten l'unicode. El següent exemple amb l'NSAutoreleasePool mostra un forma adient per fer strings i inicialitzar-los. Això és una forma de mantenir un variable membre en la gestió de la memòria. Una forma de mantenir aquest és crear copies dins dels teus mètodes.
Quan vols començar a programar utilitzant NSString i altres classe de la framework Foundation necessites un sistema més flexible. Aquest sistema és utilitzant pous autoalliberables. Quan desenvolupem aplicacions Cocoa per a Mac, el pou auto alliberable s'activa automàticament per tu. Mirem-ho en el següent exemple:
AppPool.m
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pou = [[NSAutoreleasePool alloc] init];
// Creem les dades
NSString *str1 = @"string constant";
NSString *str2 = [NSString stringWithString: @"string gestionat perl pou"];
NSString *str3 = [[NSString alloc] initWithString: @"string auto gestionat"];
// Imprimim els contadors dels strings
NSLog(@"Contador: %@ = %d\n", str1, [str1 retainCount]);
NSLog(@"Contador: %@ = %d\n", str2, [str2 retainCount]);
NSLog(@"Contador: %@ = %d\n", str3, [str3 retainCount]);
// Alliberem la memòria
[str3 release];
[pou release];
return 0;
}
La sortida d'aquest exemple és:
[Session started at 2005-09-01 17:43:03 +0200.] 2005-09-01 17:43:03.742 AppPool[2046] Contador: string constant = -1 2005-09-01 17:43:03.743 AppPool[2046] Contador: string gestionat perl pou = 1 2005-09-01 17:43:03.743 AppPool[2046] Contador: string auto gestionat = 1 Executable “AppPoolâ€? has exited with status 0.
Veient la sortida del programa veiem algunes coses. Una d'aquestes és el comptador de l'str1 que és -1. L'altre és, que només s'allibera str3, i tot el programa gestiona perfectament la memòria. La raó és que la primera constant string és afegit a l'autoreleasepool automàticament. Les altres strings es fa utilitzant stringWithString. Aquest mètode crea un string que es creat per la classe NSString, que també la posa en l'autorelease pool.
És important recordar, per una gestió de memòria correcta, que els mètodes com [NSString stringWithString: @"String"] utilitzen autorelease pool, però les mètodes alloc com [[NSString alloc] initWithString: @"String"] no utilitzen les autorelease pool per gestionar la memòria.
Hi ha dues formes per gestinar la memòria a l'Objective-C: 1) retain i release o 2) retain i release / autorelease. Per cada retain, hi ha d'haver un release O un autorelease. El següent exemple mostra el que vull dir:
Fraccio.h
... +(Fraccio*) fraccioAmbNumerador: (int) n iDenominador: (int) d; ...
Faccio.m
...
+(Fraccio*) fraccioAmbNumerador: (int) n iDenominador: (int) d {
Fraccio *ret = [[Fraccio alloc] initAmbNumerador: n iDenominador: d];
[ret autorelease];
return ret;
}
...
AppAutorelease.m
#import <Foundation/Foundation.h>
#import "Fraccio.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Creem una instància nova
Fraccio *frac1 = [Fraccio fraccioAmbNumerador: 2 iDenominador: 5];
Fraccio *frac2 = [Fraccio fraccioAmbNumerador: 1 iDenominador: 3];
// Ho imprimim
NSLog(@"La fracció 1 és: %@\n", [frac1 print]);
NSLog(@"La fracció 2 és: %@\n", [frac2 print]);
// Això causa un error de segmentació
//[frac1 release];
[pool release];
return 0;
}
El resultat de l'aplicació és el següent:
[Session started at 2005-09-01 18:01:48 +0200.] 2005-09-01 18:01:48.137 AppAutoRelease[2091] La fracció 1 és: 2/5 2005-09-01 18:01:48.141 AppAutoRelease[2091] La fracció 2 és: 1/3 2005-09-01 18:01:48.141 AppAutoRelease[2091] Esborrant una fraccio 2005-09-01 18:01:48.141 AppAutoRelease[2091] Esborrant una fraccio Executable “AppAutoReleaseâ€? has exited with status 0.
En aquest exempl, el mètode és un mètode de nivell de classe. Després que es crea l'objecte, s'auto-crida a l'autorelease. Dins del cos del mètode principal, no hem cridat cap release de l'objecte. La raó de fer-ho així és perquè: per cada retain, s'ha de cridar un release o un autorelease. El comptador comença amb 1, i s'ha cridat a la funció autorelease. Això vol dir 1-1=0. Un cop s'ha alliberat l'autorelease pool, el comptador realitza una crida autorelease a tots els objectes i els decrementa el mateix nombre de vegades que s'ha cridat l'autorelease per l'objecte.
Com diuen els comentaris, si descomentem la línia causarem un error de segment. A partir de que s'ha cridat a la funció autorelease de l'objecte, i després en cridem un release, quan fem un release de l'autorelease pool intentarem cridar a la funció dealloc d'un objecte que és nil, el qual no és vàlid. Al final queda que 1 (creació) -1 (release) -1 (autorelease) = -1.
Les autoreleasepools poden crear-se dinàmicament per una gran quantitat d'objectes temporals. Tots els objectes que han estat creats en un pool, després s'alliberen pel mateix pool. Com pots entendre, això vol dir que és impossible tenir més d'un autoreleas pool a l'hora.
La framework Foundation és similar a la Llibreria de Plantilles Estàndards del C++. Com que l'Objective-C té definició dinàmica real, les horribles plantilles del C++ ja no són necessàries. Aquesta framework conté col·leccions, xarxes, fils i molts altres.
Hi ha dos tipus d'arrays (i normalment de la majoria de classes de Foundation orientades a dades), l'NSArray i l'NSMutableArray. Com suggereix el nom, la Mutable pot canviar, i la NSArray no. Això vol dir que pot crear-se un NSArray però un cop el tens no pots canviar-ne la seva longitud.
Anem a mirar un exemple:
AppArray.m
#import <Foundation/Foundation.h>
void print( NSArray *array ) {
NSEnumerator *enumerator = [array objectEnumerator];
id obj;
while ( obj = [enumerator nextObject] ) {
NSLog( @"%@\n", [obj description] );
}
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSArray *arr = [[NSArray alloc] initWithObjects:
@"Jo", @"JoMateix", @"Io", nil];
NSMutableArray *mutable = [[NSMutableArray alloc] init];
// enumerate over items
NSLog( @"----static array\n" );
print( arr );
// add stuff
[mutable addObject: @"Un"];
[mutable addObject: @"Dos"];
[mutable addObjectsFromArray: arr];
[mutable addObject: @"Tres"];
// print em
NSLog( @"----mutable array\n" );
print( mutable );
// sort then print
NSLog( @"----sorted mutable array\n" );
[mutable sortUsingSelector: @selector( caseInsensitiveCompare: )];
print( mutable );
// free memory
[arr release];
[mutable release];
[pool release];
return 0;
}
El resultat de l'executable serà:
[Session started at 2005-09-01 18:40:54 +0200.] 2005-09-01 18:40:54.600 AppArray[2168] ----static array 2005-09-01 18:40:54.611 AppArray[2168] Jo 2005-09-01 18:40:54.611 AppArray[2168] JoMateix 2005-09-01 18:40:54.612 AppArray[2168] Io 2005-09-01 18:40:54.612 AppArray[2168] ----mutable array 2005-09-01 18:40:54.613 AppArray[2168] Un 2005-09-01 18:40:54.613 AppArray[2168] Dos 2005-09-01 18:40:54.613 AppArray[2168] Jo 2005-09-01 18:40:54.613 AppArray[2168] JoMateix 2005-09-01 18:40:54.613 AppArray[2168] Io 2005-09-01 18:40:54.613 AppArray[2168] Tres 2005-09-01 18:40:54.613 AppArray[2168] ----sorted mutable array 2005-09-01 18:40:54.614 AppArray[2168] Dos 2005-09-01 18:40:54.614 AppArray[2168] Io 2005-09-01 18:40:54.614 AppArray[2168] Jo 2005-09-01 18:40:54.614 AppArray[2168] JoMateix 2005-09-01 18:40:54.614 AppArray[2168] Tres 2005-09-01 18:40:54.614 AppArray[2168] Un Executable “AppArrayâ€? has exited with status 0.
En l'exemple inicialitzes un array mitjançant el constructor utilitzant Obj, Obj, Obj, ..., nil. El nil és un delimitador de final. L'ordenació mostra com ordenar un objecte utilitzant un selector. El selector indica a l'array qui decideix l'ordre. Si el teu objecte té diversos mètodes d'ordenació, pots escollir-ne qualsevol que vulguis utilitzant el seu selector.
En el mètode print, s'ha usat el mètode descripció. Aquest és semblant al toString de Java. Retorna una representació NSString d'un objecte. L'NSEnumerator és semblant al sistema enumerador del Java. La raó perquè el while (obj = [array objectEnumerator]) funciona és perquè objectEnumerator retorna nil en l'últim objecte. Des de C, nil és normalment 0, que és el mateix que false. Seria preferible però: ( ( obj = [array objectEnumerator] ) != nil ).
També mirarem per sobre com funciona l'NSDictionary amb el següent exemple:
AppDictionary.m
#import <Foundation/Foundation.h>
void print( NSDictionary *map ) {
NSEnumerator *enumerator = [map keyEnumerator];
id key;
while ( key = [enumerator nextObject] ) {
NSLog( @"%@ => %@\n",
[key description],
[[map objectForKey: key] description] );
}
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
@"un", [NSNumber numberWithInt: 1],
@"dos", [NSNumber numberWithInt: 2],
@"tres", [NSNumber numberWithInt: 3],
nil];
NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init];
// print dictionary
NSLog( @"----static dictionary\n" );
print( dictionary );
// add objects
[mutable setObject: @"Tom" forKey: @"tom@jones.com"];
[mutable setObject: @"Bob" forKey: @"bob@dole.com" ];
// print mutable dictionary
NSLog( @"----mutable dictionary\n" );
print( mutable );
// free memory
[dictionary release];
[mutable release];
[pool release];
return 0;
}
El resultat de l'executable serà:
[Session started at 2005-09-01 18:56:23 +0200.] 2005-09-01 18:56:23.651 AppDictionary[2216] ----static dictionary 2005-09-01 18:56:23.660 AppDictionary[2216] 1 => un 2005-09-01 18:56:23.661 AppDictionary[2216] 2 => dos 2005-09-01 18:56:23.661 AppDictionary[2216] 3 => tres 2005-09-01 18:56:23.661 AppDictionary[2216] ----mutable dictionary 2005-09-01 18:56:23.662 AppDictionary[2216] bob@dole.com => Bob 2005-09-01 18:56:23.662 AppDictionary[2216] tom@jones.com => Tom Executable “AppDictionaryâ€? has exited with status 0.