Come secondo esempio per illustrare alcune nuove caratteristiche e funzionalita' del framework di sviluppo Xcode proveremo a costruire una semplice calcolatrice, utilizzando gli oggetti ed i metodi sino ad ora appresi. Successivamente, proveremo a derivare una classe dall'oggetto UIView per contenere le funzionalita' base della calcolatrice appena creata.

Apriamo Xcode e creiamo un nuovo progetto:




Scegliamo il template Window-based Application ed un nome per il nostro nuovo progetto:




Eliminiamo il file di risorse MainWindow.xib e modifichiamo il codice sorgente di main.m, in maniera che venga assegnata programmaticamente la nostra classe CalcolatriceAppDelegate come delegato per il protocollo UIApplicationDelegate:

int main(int argc, char *argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"CalcolatriceAppDelegate");
    [pool release];
    return retVal;
}



A questo punto, possiamo procedere con l'inizializzazione a schermo della nostra applicazione. Ricordiamo che il punto di partenza del nostro codice sara' proprio il metodo applicationDidFinishLaunching:, facente parte del protocollo UIApplicationDelegate. Esso verra' interpellato non appena il sistema operativo sara' pronto per ospitare la nostra nuova applicazione.

Navighiamo il file di intestazione (.h) della classe CalcolatriceAppDelegate ed aggiungiamo le seguenti dichiarazioni all'interfaccia:

@interface CalcolatriceAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;

    UIView *myPanel;
    UILabel *myDiplay;
    NSMutableArray *operazioni;
    int resultValue;
    int selectedOperation;

}
@end



Tutti le variabili di istanza dichiarate con un segno di puntatore (*), saranno puntatori ad istanze di oggetti in memoria. Le due variabili intere, invece, sono variabili di tipo base, allocate staticamente in fase di creazione (alloc) della nostra classe.

Salviamo e spostiamo il nostro editor sul file di implementazione (.m):

- (void)applicationDidFinishLaunching:(UIApplication *)application {

    //make the window key and visible
    [window makeKeyAndVisible];
}



Aggiungiamo le allocazioni ed inizializzazioni dei nostri oggetti:

- (void)applicationDidFinishLaunching:(UIApplication *)application {

    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    myPanel = [[UIView alloc] initWithFrame:window.frame];

    //make the window key and visible
    [window makeKeyAndVisible];
}



Il metodo bounds opera analogamente al metodo applicationFrame. La differenza consiste nell'area ritornata. Questa volta, otterremo un'area pari a tutto lo schermo del nostro iPhone, indipendentemente dalla top bar presente in alto.
Una volta create le istanze degli oggetti UIWindow ed UIView, possiamo procedere con l'inizializzazione della nostra applicazione. L'istanza myPanel sara' il contenitore padre per tutti gli elementi. Essa verra' aggiunta all'istanza della UIWindow per visualizzare la nostra applicazione ed i relativi oggetti a schermo.

Utilizzando un semplice ciclo for, creiamo programmaticamente dieci bottoni con i rispettivi numeri interi. Gli oggetti UIButton verranno creati ed aggiunti alla view di riferimento ( myPanel ) dinamicamente; di conseguenza, non avremo bisogno di creare dieci variabili puntatore. A tale scopo, riutilizzeremo un'unica variabile temporanea.




Gli oggetti di tipo UIButton sono creati attraverso un metodo di classe - buttonWithType:. La memoria allocata, quindi, non dovra' essere rilasciata manualmente; il blocco NSAutoreleasePool sara' responsabile del loro rilascio una volta che non saranno piu' necessari all'applicazione (ad esempio, se su di essi viene chiamato il messaggio removeFromSuperview).

Tutti i bottoni hanno come selettore designato il metodo numberPressed:. Nell'esempio della lezione precedente ogni bottone era associato ad un selettore diverso; ogni bottone, quindi, lanciava un messaggio diverso ( @selector(firma_metodo) ). In quest'esempio, invece, utilizziamo un unico messaggio passando un parametro ( : alla fine del nome del metodo ).

Il metodo addTarget::: puo' passare due parametri al selettore designato: l'oggetto che ha lanciato il messagio - (id)sender - e l'evento che ha causato il lancio del messaggio - (UIControlEvents)event. Entrambi i parametri sono opzionali.

Allo stesso modo creiamo cinque oggetti UIButton, responsabili della scelta dell'operazione aritmetica da effettuare. L'unica differenza con il passo precedente consiste nell'inserimento di ognuna delle rispettive stringhe, rappresentative dell'operazione ( +, -, *, /, = ), in un array.




Aggiungiamo, infine, l'allocazione ed inizializzazione di un oggetto UILabel. Esso costituira' il display della nostra calcolatrice. L'oggetto e' allocato ed inizializzato a schermo con una coppia alloc/init; sara' necessario rilasciare la memoria dopo aver aggiunto l'oggetto alla UIView parent.






Utilizzo dei parametri passati dal selettore
Utilizzando il parametro (id) sender potremo gestire la pressione di bottoni differenti. A tal proposito, effettueremo un cast a tipo UIButton della variabile generica sender. Nota che ogni bottone puo' essere identificato univocamente dalla sua etichetta, rappresentante il corrispondente valore intero (currentTitle).




Le operazioni di "selezione operatore" verranno gestite da un metodo analogo:






Subclassing di UIView
Se osserviamo il codice sopra, ci accorgeremo del fatto che sarebbe molto utile ed elegante poter racchiudere tutte le operazioni di inizializzazione e controllo del tastierino e del display, attraverso un unico oggetto. A tal proposito, sara' necessario estendere l'oggetto UIView, derivandone un nuovo oggetto che chiameremo CalculatorView. Il nuovo oggetto sara' responsabile della creazione di tutti gli elementi necessari alla calcolatrice (bottoni e display) e delle operazioni ad essa associate.




Per estendere la classe UIView bastera' far seguire il suo nome al segno di due punti ( : ) nella dichiarazione di interfaccia. Tutti i puntatori ad istanze di oggetti che avevamo dichiarato nell'interfaccia della classe CalcolatriceAppDelegate, saranno spostati nella nuova dichiarazione di interfaccia; essi diventeranno parte integrante del nuovo oggetto. Al loro posto, nella dichiarazione di interfaccia del delegato, indicheremo un unico puntatore ad oggetto di tipo CalculatorView.





La classe CalcolatriceAppDelegate, non ha nozione della nuova classe introdotta in quest'esempio; per ovviare a questo problema, dovremo apportare una modifica utilizzando una nuova direttiva: @class.



La direttiva @class
L'uso della direttiva @class risulta necessario quando bisogna fornire ad una classe informazioni relative ad altre classi/oggetti, facenti parte del nostro progetto. Sebbene potremmo importare il file di intestazione, relativo alla classe a cui intendiamo riferirci, l'uso della direttiva @class risulta molto piu' efficiente. Il compilatore, infatti, non ha bisogno di processare tutto il file di intestazione; esso deve solo sapere che CalculatorView e' il nome di una classe che fa parte del nostro attuale progetto.




E' bene notare che se avremo bisogno di riferirci a metodi della classe CalculatorView e/o a sue variabili d'istanza, la direttiva @class non risultera' sufficiente. Essa, infatti, istruisce il compilatore con la nozione di forward declaration, ma non puo' sostituire l'importazione (e sua valutazione) del file di intestazione/interfaccia della classe. In questo caso, sara' necessario aggiungere al file di implementazione della classe che fa uso di una forward declaration, la direttiva di importazione della classe corrispondente.

Nel nostro caso, la classe CalculatorView non espone nessuna variabile (@property/@synthesize) o metodo d'istanza. L'importazione del suo file di interfaccia, quindi, risultera' superfluo. Tuttavia, puo' essere utile vedere la sua ubicazione all'interno del file di implementazione della classe CalcolatriceAppDelegate.




E' interessante notare come la nuova versione del metodo applicationDidFinishLaunching: sia molto piu' snella ed elegante; semplicemente, vengono allocati i due oggetti (window e myCalculatorView), necessari al corretto funzionamento dell'applicazione. La parte "pensante" e' stata spostata nella nuova classe, che eredita ed estende le proprieta' dell'oggetto UIView. Pensare ad oggetti e' un passo essenziale nella corretta progettazione ed implementazione di un'applicazione.



Overriding dei Metodi
Resta ancora da spostare il codice che prima stava nel metodo applicationDidFinishLaunching:. Per far questo, introdurremo il concetto di riscrittura o overriding di un metodo.

Se osserviamo il metodo applicationDidFinishLaunching riscritto sopra, noteremo che nell'inizializzazione dell'oggetto CalculatorView non intervengono metodi nuovi. Il "tradizionale" metodo di initWithFrame svolge egregiamente il suo dovere. Perche' cio' avvenga, tuttavia, e' necessario sovrascrivere il metodo della classe padre con una nuova versione, atta a gestire le caratteristiche proprie della nostra nuova classe.




La prima cosa da fare e' assicurarsi che l'inizializzazione della classe padre sia completata con successo. Estendere una classe significa ereditare tutte le sue caratteristiche base, aggiungendo alcune funzionalita'. Dovremo, quindi, per prima cosa inizializzare la parte ereditata dalla classe UIView. Utilizzeremo la keyword super per riferirci alla parte ereditata. Se l'inizializzazione della parte base e' completata con successo, possiamo continuare con l'inizializzazione di tutti i componenti privati.

if (self = [super initWithFrame:frame]) {
   ...
}



Alla fine, ritorneremo self : la nuova istanza di classe con tutte le sue componenti correttamente inizializzate.



Il metodo dealloc
E' necessaria un'ultima aggiunta al codice della nostra nuova classe. Il codice di inizializzazione della nuova classe non rilascia l'oggetto NSMutableArray, allocato per tenere traccia delle operazioni disponibili nella calcolatrice. Visto che trattasi di un oggetto necessario per tutta la durata di vita del nostro oggetto CalculatorView, esso andra' rilasciato solo in fase di deallocazione ( dealloc: ) della nostra classe. Il metodo dealloc, infatti, viene chiamato non appena l'oggetto e' rimosso dallo schermo con una chiamata al metodo removeFromSuperview della superclasse UIView.






Riferimenti

- Mac OS X Reference Library: iPhone Application Programming Guide
- Mac OS X Reference Library: Window and Views
- Programming in Objective-C - Capitolo 7
- Programming in Objective-C - Capitolo 8
- iPhone SDK Application Development - Capitolo 3



    lap1

downloads