MaCocoa 008

Capitolo 008 - Un programma in Objective C

Un piccolo programma finalmente Object Oriented.

Sorgenti: Tutta farina del mio sacco (si fa per dire...)

Primo inserimento: 5 Novembre 2001

Ritocchi per aggiornamento XCode 2.1: 5 Agosto 2005

Contatore

Questa volta proviamo a scrivere un'applicazione in Objective C, ma senza interfaccia utente; quindi, ancora una volta, un tool che si utilizza tramite la linea di comando. Ma il programma sarà scritto in Objective C e presenta una classe.

figura 99

figura 99

L'idea è di fare un contatore, ovvero un oggetto che contenga al proprio interno un valore che può essere incrementato, decrementato, letto o impostato ad una dato valore. Nulla di speciale, in effetti, anzi, un metodo più complicato per una cosa che in C si farebbe con una semplice variabile. Però, ci interessa giocare col concetto.

Cominciamo quindi col fare il progetto; apro XCode e scelgo, nella solita finestra dei template di progetto, un Foundation Tool; questo template dovrebbe comprendere il Foundation Framework, che contiene quindi tutte quelle classi che ci occorrono per lavorare. Manca l'Application Framework, le classi dell'interfaccia utente, ma ho appena stabilito che ne faccio a meno.

XCode propone quindi un file main.m molto semplice:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...
    NSLog(@"Hello, World!");
    [pool release];
    return 0;
}

Ignorerò quelle righe misteriose che parlano dell'oggetto pool, ed approfondisco quella strana cosa che si chiama NSLog. Cerco sul manuale (fa parte delle Foundation Functions) e trovo una cosa piuttosto interessante.

La funzione NSLog

NSLog è una funzione che scrive una NSString sullo standard error. Comincio dalle cose che so: nei sistemi Unix ogni programma ha tre file predefiniti sui quali può operare. Questi tre file si chiamano standard input, standard output e standard error oppure, per gli amici, stdin, stdout, stderr. In Unix, qualsiasi cosa è un file, ed ogni risorsa disponibile sul computer è vista come un file; non meraviglia quindi che un dispositivo di input quale la tastiera è mappato in un file denominato stdin, l'uscita a video di un programma è mappato su un file chiamato stdout, e gli errori sono scritti in un file chiamato stderr. Poi, tutto quanto è mescolato assieme, per cui stdin è in pratica rappresentato dai caratteri immessi dalla linea di comando, stdout sono i caratteri che il programma scrive sulla stessa linea di comando, e stderr va ancora una volta a finire sulla linea di comando... (ma ci sono tecniche per farne altro, ad esempio, scrivere il tutto in un vero file...).

NSLog accetta un numero variabile di argomenti. Il primo deve essere un oggetto di tipo NSString con al proprio interno dei codici di formattazione; questi codici devono corrispondere poi agli altri argomenti. Per farla breve, funziona più o meno come la funzione di libreria standard del C printf, tanto cara ed utile a tutti i programmatori, ma usa NSString al posto delle stringhe C (e qualche altra piccola differenza).

Parentesi. Ed allora, perché non uso printf e non ci penso più? Ma perché sto programmando in Objective C e Cocoa, ed il programma non utilizza in linea teorica la libreria standard del C che mette a disposizione la funzione printf. Chiusa parentesi.

Vediamo a questo punto cosa diavolo è una NSString: il sospetto che si tratti di una stringa (insieme di caratteri) è forte. Infatti. Trovo pagine e pagine su NSString, a quanto pare uno degli oggetti più importanti del Foundation Framework (ma le stringhe sono in generale uno dei costrutti più importanti di ogni linguaggio di programmazione...). Senza approfondire la vita ed i miracoli dell'oggetto NSString, vedo che quest'oggetto conserva nelle sue variabili d'istanza stringhe di testo rappresentate secondo la codifica Unicode (e quindi tratta tranquillamente caratteri in tutte le lingue del mondo), ed ha a corredo una magnifica teoria di metodi adatti a molte bisogna. Cito alla rinfusa metodi per accedere ai singoli caratteri, per sapere quanto è lunga la stringa, per assegnare il valore a partire dai costrutti più disparati, per combinarne più assieme, per spezzarle in diverse altre NSString, per fare ricerche e confronti, estrarne numeri; di più, ci sono tutta una serie di metodi da usare quando la stringa è in realtà un path, un url o comunque un nome di file completo.

Finalmente arriviamo alla cosa più esotica, ovvero il costrutto @"". Si tratta di un metodo di convenienza supportato dal compilatore ObjC per la costruizione di oggetti NSString. Con questo operatore è possibile definire al volo oggetti di tipo NSString; per il momento non serve sapere altro.

Proviamo NSLog

Riassumendo: con NSLog posso scrivere delle stringhe sulla riga di comando; queste stringhe sono oggetti di tipo NSString, che si possono definire in linea coll'operatore @"". La stringa può contenere codici di controllo, funzionando come una printf della libreria standard del C. La provo subito, e faccio un programma piuttosto stupido.

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...
    float    a = 10.34 ;
    int        b = -76546 ;
    char *    c = "salve a tutti";
    
    NSLog(@"______1234567890_1234567890123456_");
    NSLog(@"aloha_%10.5f_%16d_%.10s_", a, b, c);
    [pool release];
    return 0;
}

La prima chiamata a NSLog mi serve per mettere una sorta di righello, per controllare che le specifiche di formattazione dei campi nella seconda NSLog corrispondano. Infatti nella seconda NSLog dico di scrivere un numero float in dieci colonne utilizzando cinque cifre decimali, un intero su sedici colonne, e poi una stringa C normale. tutto torna: infatti il programma produce:

[Session started at 2005-08-05 17:41:28 +0200.]
2005-08-05 17:41:29.717 mc008[1122] ______1234567890_1234567890123456_
2005-08-05 17:41:29.719 mc008[1122] aloha_  10.34000_          -76546_salve a tu_

mc008_1 has exited with status 0.

Il che ci permette di fare un veloce commento: NSLog aggiunge, di suo, davanti al messaggio che gli diciamo di scrivere, la data, l'ora, il nome del programma ed il suo pid (process identifier, l'identificatore numerico del programma).

Come per la funzione printf dello standard C, nella stringa di formato si possono inserire delle sequenze di controllo che cominciano col carattere %, seguite da un carattere che indica il tipo di valore da scrivere. Ricordo, ad esempio, la lettera "d" per scrivere valori decimali, la lettera "f" per numeri floating point, eccetera (ogni buon manuale di C vi illustrerà la cosa nei dettagli, dilungandosi sulla possibilità di specificare un formato, un'ampiezza, bazzeccole del genere.

Di suo, NSLog aggiunge la possibilità di scrivere "Oggetti", ed in particolare NSString, con la specifica di formato data dal carattere "@". Come mi precisa Mirko Viviani, nel caso di oggetti, i caratteri "%@" producono come risultato della valutazione di formato una stringa che rappresenta il risultato del metodo description applicato all'oggetto stesso. Tale metodo restituisce per gli oggetti semplici (numeri e stringhe...) il valore dell'oggetto stesso, mentre per oggetti più complicati (ad esempio NSArray) descrizioni più complesse. È ovviamente possibile, per la classi definite dall'utente, definire il metodo description in maniera più o meno fantasiosa ed utilizzarlo come strumento di debug.

C'è una cosa da notare, e che mi era ovviamente sfuggita. NSLog scrive la stringa sullo stderr, che NON è ridiretto di default sulla linea di comando, ma sulla Console. Ora, qualche ulteriore parola va impiegata. Finché, come abbiamo fatto, il nostro progetto non è una applicazione Cocoa, va fatta partire da linea di comando; stdin, stdout e stderr coincidono (a meno di ridirezione esplicita... una cosa che ci porta troppo lontano...) con la linea di comando, per cui si vedrà tutto tranquillamente sul Terminale aperto. Quando costruisco una applicazione Cocoa, se la lancio con un doppio clic e comunque non da linea di comando, stdin, stdout e stderr perdono di significato (non ho un Terminale cui fare riferimento). In questi casi, quando qualche applicazione intende scrivere qualcosa, esiste appunto un file, console.log, dove va a finire tutto quanto. Per vedere questo file, un metodo semplice è di utilizzare l'applicazione Console, nella cartella /Applicazioni/Utilities. Lanciando Console, trovate un sacco di messaggi (molta roba vecchia, ma anche recente, messaggi di errori sconosciuti, porcherie sparse...). Quando faremo un'applicazione Cocoa lanciata da doppio clic, qui compaiono le scritte di NSLog.

Non è finita: se lancio l'applicazione scritta con Cocoa direttamente da Terminale (si può fare...), stderr ritorna sul Terminale...

L'applicazione

figura 01

figura 01

figura 02

figura 02

Torniamo a bomba, ovvero alla costruzione di una applicazione in Objective C. Bisogna definire una classe Contatore, dichiarare le variabili d'istanza e definire i metodi. La variabile d'istanza sarà una sola, ovvero una variabile che contiene un numero intero che fa da contatore. Per quanto riguarda i metodi, ci occorre un metodo per leggere il valore del contatore, uno per impostarlo, uno per incrementare il contatore ed uno per decremtnare il contatore. Facciamo partire il Xcode e scegliamo il menu New File.... Oh, che bello, mi mostra un dialogo che mi chiede il tipo di file che voglio fare. Mi pare ovvio che sceglierò una Classe in Objective C. Il risultato dell'operazione, una volta inserite le informazioni richieste (mi va bene quelle di default, ovvero costruire anche il file .h corrispondente, l'inserimento nel progetto e nel target attivo), è particolarmente carino. Xcode ha infatto già creato per me due file, il .m ed il .h, già riempiti con dei suggerimenti per la costruzione della classe.

Ecco quindi, molto semplicemente, i due file. Il file Contatore.h

#import <Foundation/Foundation.h>

@interface Contatore : NSObject {
    short    counter ;
}
- (void)    setValue: (short) startVal ;
- (short)    getValue ;
- (void)    countUp ;
- (void)    countDown ;
@end

e questo è il file Contatore.m:

#import "Contatore.h"

@implementation Contatore
-(void) setValue: (short) valore
{
    counter = valore ;
}

-(void) countUp
{
    counter ++ ;
}


-(void) countDown ;
{
    counter -- ;
}

- (short) getValue
{
    return ( counter );
}
@end

Utilizzo questa classe in maniera semplice, nel file main.m

#import <Foundation/Foundation.h>
#import    "Contatore.h"

int main (int argc, const char * argv[])
{
    // lascio stare questa istruzione, che non so cosa sia...
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    // definisco il mio oggetto
    Contatore    * miocont ;

    // diciamo che stiamo per partire...
    NSLog(@"La mia prima classe");
    // costruisco ed inizializzo il mio oggetto
    miocont = [[Contatore alloc] init ];
    // utilizzo un metodo per vedere cosa c'e' dentro
    NSLog( @"Valore = %d\n", [miocont getValue ]);
    // adesso imposto il suo valore a 10
    [ miocont setValue: 10 ];
    // controllo che sia proprio cosi'
    NSLog( @"Valore = %d\n", [miocont getValue ]);
    // poi a venti...
    [ miocont setValue: 20 ];
    NSLog( @"Valore = %d\n", [miocont getValue ]);
    // incremento il contatore
     [ miocont countUp ];
    NSLog( @"Valore = %d\n", [miocont getValue ]);
    // lo descremento
     [ miocont countDown ];
    NSLog( @"Valore = %d\n", [miocont getValue ]);

    // lascio il resto com'e'
    [pool release];
    return 0;
}

Compilo (neppure un errore!) l'intero progetto ed eseguo. Ottengo:

Nov 02 14:01:49 mc008_2[457] La mia prima classe
Nov 02 14:01:49 mc008_2[457] Valore = 0
Nov 02 14:01:49 mc008_2[457] Valore = 10
Nov 02 14:01:49 mc008_2[457] Valore = 20
Nov 02 14:01:49 mc008_2[457] Valore = 21
Nov 02 14:01:49 mc008_2[457] Valore = 20

mc008_2 has exited with status 0.

Ottimo. Tutto funziona. Fin troppo facile.

Adesso, apriamo una finestra e visualizziamo questo contatore da qualche parte...

Temo che non sarà una cosa immediata...

Licenza Creative Commons
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto Licenza Creative Commons.
Pagina a cura di Livio Sandel (macocoa2012@gmail.com).