Tutorial Objective-C.pdf

Embed Size (px)

Citation preview

  • 7/30/2019 Tutorial Objective-C.pdf

    1/46

  • 7/30/2019 Tutorial Objective-C.pdf

    2/46

    2

    Introduzione al linguaggio Objective-C

    Piano delloperaChe cosa faremo

    Che cosa non faremo

    1. Programmare ad oggettiLinguaggi procedurali e linguaggi ad oggettiQuando programmare ad oggettiPerch Objective-C

    2. Objective-C e C++Objective-C meglio di C++C++ meglio di Objective-CLa quadratura del cerchio

    3. Classi ed OggettiIl mondo delle idee e il mondo realeInterfaccia di una classeImplementazione di una classeEseguire un programma

    4. Sottoclassi

    5. Categorie e ProtocolliCategorieProtocolli formaliProtocolli informali

    Bibliografia

  • 7/30/2019 Tutorial Objective-C.pdf

    3/46

    3

    Piano dellopera

    Proseguendo nella tradizione inaugurata col primo volume di questopera, lormai famosaIntroduzione al Linguaggio C, ho deciso di dare ancora una volta questo titolo altisonanteallintroduzione di questo secondo volume, perch, diciamocelo, ma fa sentire importante!Scherzi a parte, ora il caso di fare un breve riassunto di che cosa voglia essere questa

    Introduzione al Linguaggio Objective-Ce a chi si rivolge.Come gi per il primo volume, questo secondo manuale rivolto ad un pubblico di

    principianti, che non hanno esperienze di programmazione in Objective-C o che forse nonhanno esperienze di programmazione in generale. Gli argomenti trattati saranno di base, mentrequelli pi ostici o pi avanzati saranno lasciati allottima letteratura presente in commercio o inrete, un sunto della quale potrete trovarlo nella bibliografia. In ogni caso, necessario cheabbiate almeno uninfarinatura di C; se non lavete, vi consiglio caldamente di leggervi qualchetesto di base, inclusa lIntroduzione al Linguaggio C, che potete reperire alla stessa pagina webdalla quale avete scaricato questo volume sullObjective-C.

    Anche in questo caso i primi due capitoli sono essenzialmente filosofici: non entreremonei dettagli del linguaggio, ma ci limiteremo a discutere brevemente su che cosa voglia dire

    programmare ad oggetti, e sul perch questo sia talvolta pi facile e talvolta pi difficile cheprogrammare proceduralmente, come si fa col C e con molti altri linguaggi. Un breve etuttaltro che omnicomprensivo confronto col C++ sar doveroso nel capitolo 2, dal momentoche il C++ forse il linguaggio ad oggetti pi largamente usato e che trova il maggior numerodi estimatori.

    Dal capitolo 3 inizieremo il lavoro sporco, partendo dai mattoni di base di qualunquelinguaggio ad oggetti, le classi e, per lappunto, gli oggetti. Bench tutto quello che diremo siain linea di principio trasportabile quasi senza modifiche su qualunque sistema operativo cheabbia un compilatore Objective-C (come Linux, ad esempio), lattenzione sar qui rivolta quasiesclusivamente a MacOS X, di cui inizieremo ad usare alcuni degli strumenti che Apple hamesso a disposizione dei programmatori. Questo ci consentir di scrivere del codice pisemplice e pi pulito, e di iniziare a ridurre le distanze con Cocoa, lambiente di sviluppo di

    applicazioni nativo di MacOS X che sar oggetto del terzo volume di questa saga.Nel capitolo 4 vedremo come e quando abbia senso creare delle sottoclassi. Nel capitolo 5vedremo invece quando non sia necessario creare una sottoclasse e sia invece pi opportunousare strumenti alternativi come categorie eprotocolli per estendere le funzionalit di una classeesistente. Non ci occuperemo, invece, di argomenti pi ostici e non necessari (a mio parere) achi si avvicina per la prima volta ad Objective-C senza avere particolari esperienze diprogrammazione in altri linguaggi, come lintrospezione, una gestione particolarmente avanzatadella memoria, lambiente di runtime (che rende Objective-C un linguaggio estremamenteversatile).

    Tutti gli esempi presentati in questa Introduzione al Linguaggio Objective-C sonooriginali, autocontenuti e funzionanti. Sono stati tutti compilati con gcc version 3.3 20030304(Apple Computer, Inc. build 1495) incluso in XCode 1.1 di MacOS X 10.3.2.

    Che cosa faremoQuesto vuole essere un tutorial di base per lapprendimento dei fondamenti

    dellObjective-C. adatto a chi sa poco o nulla di programmazione ma interessatoallargomento. richiesta una conoscenza di base del C; lapplicazione di quanto qui imparato,bench possibile virtualmente su ogni sistema operativo dotato di compilatore Objective-C, sarcomunque esplicitamente rivolta a MacOS X.

    Che cosa non faremo

    Non ci addentreremo (ancora) nella discussione delle classi contenute nelle librerie Cocoadi MacOS X, n degli aspetti pi profondi e pi difficili della gestione della memoria edellambiente runtime di Objective-C.

  • 7/30/2019 Tutorial Objective-C.pdf

    4/46

    4

    1. Programmare ad oggetti

    Linguaggi procedurali e linguaggi ad oggetti

    La diatriba tra i linguaggi cosiddetti procedurali come il C e i linguaggi cosiddetti adoggetti o object oriented come il C++ o lObjective-C vecchia grosso modo quanto ilinguaggi stessi di programmazione. Infatti, non appena le risorse hardware sono statesufficientemente potenti da consentire lo sviluppo e lesecuzione di programmi e sistemioperativi un po pi che banali, le due filosofie di pensiero si sono scontrate, alleate esovrapposte. Non bisogna pensare che siano una lantitesi dellaltra, perch sarebbe scorretto eriduttivo. Sono piuttosto luna il complemento dellaltra, e difficilmente si pu essere dei puristida questo punto di vista. Tuttavia, i linguaggi ad oggetti hanno spesso un vantaggio rispetto aquelli procedurali: essi, infatti, consentono linserimento anche massiccio di codiceprocedurale al loro interno, mentre i linguaggi procedurali in genere non consentono luso dioggetti. Ma ora cerchiamo di capire di che cosa stiamo parlando.

    Il C, labbiamo visto nel primo volume, un linguaggio procedurale. Ci che volete farecol vostro programma descritto in una o pi funzioni che operano su dei dati, immagazzinatinelle variabili del programma; esse saranno variabili numeriche, array di caratteri, o strutture,ma in ogni caso i dati su cui opera il programma sono sempre identificabili con le sue variabili(anche un file , alla fine della fiera, una variabile allinterno del programma). Invece, il modocon cui manipolate i dati rappresentato dalle funzioni di cui costituito il programma. Se i datiche avete a disposizione sono di vario tipo e richiedono, di conseguenza, trattamenti specificiper ogni tipologia, sar compito del vostro programma distinguere un tipo di dati da un altro echiamare lopportuna funzione per il loro trattamento.

    In un linguaggio ad oggetti, invece, lapproccio diverso. Il programma, in s, non sanulla di dati e di funzioni per manipolare gli stessi. Il programma sa solo che lutente vuolepoter maneggiare un certo numero di oggetti in un certo numero di maniere pi o menopredefinite. Come questo avvenga non compito del programma saperlo. compito deglioggetti. Un oggetto , se volete, unestensione del concetto di struttura del C:

    simultaneamente una collezione di dati (e quindi di variabili), ma anche di funzioni atte alla loromanipolazione. In un programma ad oggetti, il programma stesso ubbidisce alla richiestadellutente del tipo prendi questi dati e fai questa operazione; ma se i dati sono di tipo diverso,e se di conseguenza loperazione da fare diversa a seconda del tipo, non c bisogno di unaverifica preliminare allinterno del programma, come avviene per i linguaggi procedurali;invece, ogni oggetto sa che cos e che cosa in grado di fare; di conseguenza, ogni oggetto,quando gli verr chiesto di eseguire la tale operazione sui suoi dati, sapr esattamente comeimplementarla correttamente, perch ha piena coscienza di s stesso, del tipo di dati che stamaneggiando e di che cosa ci si aspetta da lui.

    Messa cos sembra una roba piuttosto complicata, e in effetti, da un certo punto di vista,lo . Per chi viene da un linguaggio procedurale come il C (come accaduto a me a suo tempo),il passaggio ad un modo di pensare ad oggetti pu essere traumatico e faticoso. In realt,

    bisogna solo trovare la guida giusta: finch non si trova il libro, il tutorial, il manuale ecc. cheaffronta il problema della programmazione ad oggetti nel modo in cui ci viene congenialevederlo affrontato, triboleremo. Per questo non so se questa Introduzione allObjective-Cpiacer a tutti: perch la scriver cos come a me pare che sia chiaro largomento, ma potrebbenon essere cos per tutti. Se non avete grosse esperienze di programmazione alle spalle,probabilmente non farete una grande fatica ad imparare lObjective-C. Se invece avete parecchiaesperienza con linguaggi procedurali e nessuna con linguaggi ad oggetti, potreste fare pifatica. Non demordete: il salto molto meno duro di quanto sembri inizialmente; e, se conlinguaggi tipo il C++, una volta compiuto il salto, potebbe venirvi voglia di tornare indietro, vigarantisco che con lObjective-C questa tentazione non vi verr mai!

  • 7/30/2019 Tutorial Objective-C.pdf

    5/46

    5

    Quando programmare ad oggetti

    La soluzione a tutti i mali, purtroppo, non esiste. Questo vero anche quando si parla dilinguaggi di programmazione. Bench si senta spesso dire che la programmazione ad oggetti migliore di quella procedurale, pi moderna, da preferirsi, ecc., questo non sempre e non

    necessariamente vero. Come al solito,dipende

    .Se il programma che volete fare piccolo, pi che altro una prova, o un sempliceprogrammino che fa un compito ben preciso e limitato, sempre uguale, forse un linguaggioprocedurale meglio. Se invece state realizzando un programma pi ampio, il cuicomportamento maggiormente variabile, in cui linput dellutente meno scontato di quanto sipotrebbe pensare, forse gli oggetti fanno per voi. Se volete effettuare conti su conti, simulazioninumeriche e cose simili, la programmazione procedurale vi sar forse pi congeniale. Se inveceavete la necessit di manipolare vari soggetti pi o meno indipendenti, come le figuregeometriche di un disegno tecnico o i personaggi di un gioco di ruolo, gli oggetti potrebberoessere la vostra salvezza.

    Non c una ricetta generale per stabilire se per realizzare un certo programma sia meglioragionare proceduralmente o ad oggetti. A mio avviso, per risolvere questo dilemmaoccorrono due cose: conoscere almeno un linguaggio procedurale ed uno ad oggetti, ed amarli

    entrambi. Se non ne conoscete almeno uno per tipo, il problema non si pone, perch nondisponete degli strumenti necessari per affrontare il problema nelle due maniere alternative; senon ne amate almeno uno per tipo, la vostra scelta sar sempre condizionata dai vostri gusti, chemagari nascono da una non corretta scelta del linguaggio di programmazione con cui affrontareil problema. Non ho la pretesa di poter sempre risolvere la questione. Ma se sapete un po di C,allora avete idea di che cosa pu fare e come lavora un linguaggio procedurale. Se sapete un podi C++ o di Objective-C, allora avete idea di che cosa pu fare e come lavora un linguaggio adoggetti.

    Perch Objective-C

    Molti vi diranno che il C++ , tra i linguaggi ad oggetti, il pi potente, il pi diffuso, ilpi bello, il pi qui e il pi l. Non contesto n discuto queste affermazioni. Ma allora, perch

    Objective-C? Per almeno 3 ragioni:1. un linguaggio ad oggetti, quindi perch no?2. il linguaggio che Apple ha scelto per lo sviluppo di applicazioni di alto livello su

    MacOS X. Pu piacere o no, ma un fatto. E siccome, alla fine del gioco, vogliamo arrivare aprogrammare su MacOS X, Objective-C una scelta quanto meno sensata.

    3. Non sar potente e diffuso come il C++, ma secondo me lo supera, e di gran lunga,sotto molti punti di vista. Vedremo sommariamente il perch nel prossimo capitolo. Vi bastisapere che, pur mancando di alcune delle caratteristiche pi avanzate del C++, lObjective-Crinuncia ai difetti del C (che invece il C++ eredita tutti, e alcuni li peggiora), e guadagna nuovecaratteristiche che lo rendono un linguaggio a mio modo di vedere meraviglioso.

    Padroni di pensarla diversamente. Ma se siete nuovi della programmazione ad oggetti,

    credo che troverete lapprendimento dellObjective-C molto pi facile e molto pi entusiasmantedellapprendimento del C++. Provare per credere!

  • 7/30/2019 Tutorial Objective-C.pdf

    6/46

    6

    2. Objective-C e C++

    Objective-C meglio di C++

    E chi lha detto? Lo dico io! Ma non del tutto vero. Objective-C ha senza dubbio unasintassi pi semplice e pi chiara rispetto al C++. Facciamo un esempio: vogliamo chiamare unafunzione (un metodo, dovremmo dire) che calcola larea di un trapezio. Vi ricordo la formula:larea del trapezio la somma delle basi, moltiplicata per laltezza, diviso 2. In C++ (e in C),dovremmo chiamare una funzione con del codice fatto pi o meno cos:

    area=AreaTrapezio(baseMagg,baseMin,altezza);

    L per l chiaro. Il problema che se vado a vedere il prototipo della funzioneAreaTrapezio(), esso definito come double AreaTrapezio(double,double,double),che, a ben vedere, non ci dice molto; infatti, non ci dice in quale ordine dobbiamo inserire le tregrandezze da passare come argomento, ovvero la base maggiore, la base minore e laltezza.

    In Objective-C la faccenda pi semplice. La stessa funzione la chiameremmo conunespressione di questo genere:

    area=[self areaConBaseMagg:baseMagg baseMin:baseMin altezza:altezza];

    A parte il fatto che difficilmente scriveremo mai una cosa di questo genere (sia in C++che in Objective-C scriveremmo, semmai, un oggetto di classe Trapezio che abbia un metodochiamato Area), il prototipo della funzione in Objective-C del tipo

    -(double)areaConBaseMagg:(double) baseMin:(double) altezza:(double);

    Come vedete, il nome stesso della funzione contiene non solo una descrizione di che cosa

    fa la funzione stessa (area), ma anche una descrizione di che cosa sono i vari argomenti; infatti,i vari ConBaseMagg:, baseMin: e altezza: sono parte integrante del nome della funzione, enon possono essere omessi. Bench, a ben vedere, ci sia pi roba da scrivere, un programma inObjective-C pi semplice da leggere, perch anche quando le variabili che si passano adargomento non hanno nomi molto evocativi, i nomi delle funzioni sono in generesufficientemente completi da permettere di capire che cosa facciano le funzioni stesse e che cosasiano i loro argomenti. Magari non vi sembra un grande miglioramento, ma quando rimetteretemano a del codice che avete scritto qualche mese prima o, peggio ancora, scritto da altri,ringrazierete che la sintassi dellObjective-C sia cos evocativa.

    Unaltra caratteristica che rende lObjective-C migliore del C++ il fatto che unlinguaggiostrongly typedma pu anche non esserlo. Abbiamo detto, del C, che stronglytypedperch una variabile pu contenere solo valori dello stesso tipo della variabile stessa; se ilvalore di un tipo diverso e se una qualche regola di promozione o di troncamento applicabile, il valore viene promosso o troncato cos da combaciare col tipo della variabile. Sequesto non possibile, il compilatore genera un errore. Nel C++ grosso modo la stessa cosa.NellObjective-C anche. Solo che con lObjective-C potete rinunciare a questo controllopreventivo dei valori e delle variabili ed assegnare alle variabili stesse tipi generici, che possonocontenere di tutto. Naturalmente, durante la compilazione, qualunque cosa decidiate di assegnarea queste variabili andr sempre bene. Dove sta il vantaggio? Sta nel fatto che potete trattare conla stessa variabile e con la stessa porzione di codice i casi pi disparati, ovvero i casi in cuinella vostra variabile sono contenuti valori (in Objective-C saranno pi propriamente deglioggetti) di tipo molto differente, che in C o in C++ richiederebbero variabili e porzioni di codicedistinte per essere trattati; ancora meglio: nella vostra variabile potete inserire valori (o oggetti)di tipo (classe, diremo per gli oggetti) che ancora non esisteva quando avete compilato il

    programma, ma che sono stati creati durante lesecuzione stessa del programma per effetto di

    un comando dellutente. Una potenzialit immensa: il vostro programma espandibile durante

  • 7/30/2019 Tutorial Objective-C.pdf

    7/46

    7

    la sua stessa esecuzione! Naturalmente questo introduce da un lato alcuni livelli dicomplicazione e richiede che il codice sia opportunamente progettato per trarre vantaggio daquesta caratteristica senza risultare confuso, ma dallaltro, una volta presa labitudine,costituisce una semplificazione impressionante nel modo di programmare. Una vera manna dalcielo. Nei prossimi capitoli cercheremo di vedere perch.

    C++ meglio di Objective-CE chi lha detto? Lo dicono in molti. Ma non del tutto vero. Per non bisogna

    dimenticare che il C++ ha degli indubbi vantaggi rispetto allObjective-C. Sono molti, macercher di dare una breve rassegna. Innanzitutto il C++ supporta le eredit multiple , ovverouna classe pu ereditare da pi classi anzich solo da una come in Objective-C (che persupporta protocolli e categorie proprio per ovviare a questo limite). Poi il C++ supporta un

    polimorfismo molto pi ampio rispetto a quello supportato dallObjective-C. Polimorfismo vuoldire che pi funzioni possono avere lo stesso nome ma avere un diverso insieme di argomenti.Questo in C standard vietatissimo: ogni funzione deve avere un nome unico e ben definito, enon possono esistere due funzioni con lo stesso nome e un diverso insieme di argomenti. InC++ questo consentito, anzi, utile: ad esempio si potrebbe voler espandere la funzionedouble sin(double x) fornita con le librerie matematiche e definita nella header math.h in

    modo da poterla usare anche quando largomento non un numero reale ma un opportunamentedefinito numero complesso, secondo un prototipo di questo tipo:

    complex sin(complex x);

    evidente che il nome della funzione viene mantenuto identico per far s che quando,allinterno del codice, si chiama la funzione sin(), non occorre preoccuparsi se largomento siaun numero reale o un numero complesso; la corretta funzione specifica per largomento passatoverr automaticamente eseguita; naturalmente compito del programmatore scrivere la funzionesin() nel caso in cui largomento sia una variabile di tipo complex; anche questultimo deveessere opportunamente definito dal programmatore. Questo comodo e, una volta presa lamano, anche abbastanza intuitivo. LObjective-C supporta qualche cosa di simile grazie alla

    possibilit di assegnare tipi generici alle variabili (anche a quelle passate per argomento allefunzioni), ma non esattamente la stessa cosa.Forse (a mio modo di vedere) la caratteristica del C++ che pi manca allObjective-C la

    possibilit di assegnare polimorfismo persino agli operatori. Ad esempio, se avete due numerireali e li volete sommare e volete che il risultato sia assegnato ad una variabile reale, in Cscrivereste qualche cosa del tipo:

    a = b + c;

    e il gioco sarebbe fatto. Le regole di promozione o di troncamento assicurano cheloperatore+ agisca indifferentemente su variabili di tipo int, short, float, double ecc., e ilrisultato sia coerente con loperazione fatta. Tuttavia, se avete definito un numero complesso a

    esempio come una struttura, siete costretti ad eseguire la somma di due numeri complessi adesempio con una funzione del tipo:

    int SommaComplessa(complex addendo1,complex addendo2, complex *risultato);

    il che abbastanza scomodo; soprattutto se volete sommare un numero reale ad unnumero complesso, perch vi toccherebbe definire unaltra funzione specifica per quel caso; evia discorrendo. Non sarebbe molto pi bello se poteste scrivere direttamente a = b + c conalmeno una delle tre variabili definita come numero complesso? Questo in C non lo potete fare,ma in C++ s. E in Objective-C no. Non una cosa che serve spesso, ma pu essere moltocomoda. A mio parere, lunica vera limitazione che lObjective-C ha rispetto al C++. Per ilresto, pi o meno tutte le caratteristiche del C++ sono disponibili (anche se in forma diversa e

    con nomi diversi) in Objective-C; il viceversa, per, non sempre vero.

  • 7/30/2019 Tutorial Objective-C.pdf

    8/46

    8

    La quadratura del cerchio

    E allora? difficile dire se sia meglio C++, Objective-C o qualche altro linguaggio.Francamente, non credo nemmeno che sia importante stabilirlo. Ogni problema che necessita diun programma per essere risolto andr affrontato con gli strumenti opportuni, che potranno

    chiamarsi C, C++, Objective-C, Perl, Python, BASIC, ecc. Pi linguaggi conoscete, pistrumenti avrete nella vostra cassetta degli attrezzi, meglio verr il lavoro che dovete svolgere.Se avrete voglia, qui parleremo di Objective-C e cercheremo di comprenderne i pregi, il

    tutto nellottica di avere delle basi sufficienti per affrontare il terzo volume della saga, in cui,finalmente, creeremo dei veri programmi per MacOS X, con tanto di interfaccia grafica in purostile Aqua.

    Ora non pi tempo di cincischiare. Allacciate le cinture e preparatevi al capitolo piostico: quello su classi ed oggetti!

  • 7/30/2019 Tutorial Objective-C.pdf

    9/46

    9

    3. Classi ed oggetti

    Il mondo delle idee e il mondo reale

    Soddisfatto dal vostro eccellente lavoro (si veda lEsempio 22 dellIntroduzione al linguaggioC), il Servizio Meteorologico Nazionale vi incarica di mettere mano al vostro programma, diaggiungere nuove funzioni, di renderlo pi versatile e, soprattutto, di renderlo facilmenteespandibile. Per fare questo, oltre ad offrirvi un sacco di soldi, vi d anche la possibilit discegliere in quale linguaggio scrivere il programma. Voi, dal momento che state leggendoqueste pagine, decidete ovviamente di scriverlo in Objective-C, perch ha tutte le caratteristichenecessarie per soddisfare le richieste del committente.

    Infatti, scrivendo il programma in Objective-C, potrete sfruttare appieno le potenzialit diun modo di lavorare ad oggetti, in cui le vostre strutture dati sono intrinsecamente legate allefunzioni che dovranno manipolarle. Gioiosi, lieti e festanti, iniziate pertanto a pensare allastruttura ad oggetti e classi del vostro programma.

    Ma che cosa sono, in effetti, oggetti e classi?Pensate ad una sedia, ad esempio quella su cui siete seduti. indiscutibilmente una sedia, dotata di tutte le caratteristiche che una sedia deve avere. Eppure diversa da quella che cnellaltra stanza. Anche quella indiscutibilmente una sedia, anche quella dotata di tutte lecaratteristiche che una sedia deve avere; eppure, le due sedie sono diverse luna dallaltra. Ledue sedie sono oggetti. Il concetto di sedia, invece, ovvero linsieme di propriet,caratteristiche e requisiti che una sedia vera e propria deve avere una classe.

    In Objective-C, quindi, parleremo di classi quando vorremo descrivere un particolaremodo di immagazzinare dati e manipolarli, parleremo di oggetti (appartenenti ad una certaclasse) quando prenderemo in considerazione un ben preciso insieme di dati che verrannoimmagazzinati e manipolati con le modalit previste dalla classe a cui appartiene loggetto. Eccoquindi una nozione importante: ogni oggetto un caso particolare di una classe; diremo che unistanza di una classe. Esistono le sedie (classe), che devono avere certi requisiti e fare certecose. La sedia su cui sono seduto io (oggetto) un caso particolare, unistanza della classesedie.

    Quando si progetta un programma ad oggetti, pertanto necessario chiedersi innanzituttosu quali dati dovr operare il programma, chiedersi come sia possibile suddividerli in oggetti,quindi definire le classi a cui questi oggetti apparterranno; solo quando si avranno chiare inmente queste idee sar possibile iniziare a scrivere il programma.

    Nel nostro caso, il Servizio Meteorologico Nazionale ci ha chiesto un programma chepermetta di fare le seguenti cose: definire un certo numero di localit; inserire, per ogni localit,temperatura e umidit a mezzogiorno di ogni giorno; mantenere una media aggiornata delletemperature e umidit inserite. I nostri oggetti saranno pertanto le varie localit, con le rispettivetemperatura e umidit medie. La classe a cui apparterranno i nostri oggetti definir un certonumero di variabili, in cui saranno memorizzati nome della localit, temperatura media, umidit

    media, e quantaltro servir, e un certo numero di funzioni o metodi, il cui compito sar quellodi gestire e manipolare queste variabili e permettere linserimento dei dati e la comunicazione deirisultati.

    Aprite pertanto il vostro editor di testo preferito (SubEthaEdit, mi, ProjectBuilder,XCode, BBEdit Lite, TextEdit, o qualunque altro editor di solo testo vi piaccia), e digitate ilseguente codice:

    #import

    #define YES 1#define NO 0

    @interface localita : NSObject

  • 7/30/2019 Tutorial Objective-C.pdf

    10/46

    10

    {NSString *nome;double temperaturaMedia;double umiditaMedia;int numeroGiorni;

    }

    - (id)initWithName:(NSString *)unNome;- (NSString *)nome;- (void)nuoviValori;- (void)mostraValori;- (void)nuovaTemperatura:(double)unaTemperatura;- (void)nuovaUmidita:(double)unaUmidita;

    @end

    Salvatelo col nome localita.h, quindi create un nuovo documento col vostro editor di testo

    preferito e digitate quanto segue:

    #import "localita.h"

    @implementation localita

    - (id)initWithName:(NSString *)unNome{

    [super init];nome=[[NSString alloc] initWithString:unNome];temperaturaMedia=0.0;umiditaMedia=0.0;numeroGiorni=0;return self;

    }

    - (void)dealloc{

    [nome release];[super dealloc];

    }

    - (NSString *)nome

    {return nome;

    }

    - (void)nuoviValori{

    double t,u;

    printf("\n");printf("Citta': %s\n",[nome cString]);printf("Inserisci la temperatura di oggi a mezzigiorno (gradi C): ");

    scanf("%lg",&t);

  • 7/30/2019 Tutorial Objective-C.pdf

    11/46

    11

    printf("Inserisci l'umidita' di oggi a mezzigiorno: ");scanf("%lg",&u);[self nuovaTemperatura:t];[self nuovaUmidita:u];

    }

    - (void)mostraValori{

    printf("\n");printf("Citta': %s\n",[nome cString]);

    printf("%s: temperatura media: %g (gradi C) e umidita' media:%g\n",[nome cString],temperaturaMedia,umiditaMedia);

    printf("Le medie sono state calcolate per un totale di %dgiorni\n\n",numeroGiorni);}

    - (void)nuovaTemperatura:(double)unaTemperatura

    {temperaturaMedia=(temperaturaMedia*numeroGiorni+unaTemperatura)/(numero

    Giorni+1);numeroGiorni++;

    }

    - (void)nuovaUmidita:(double)unaUmidita{

    umiditaMedia=(umiditaMedia*(numeroGiorni-1)+unaUmidita)/numeroGiorni;}

    @end

    Salvatelo col nome localita.m (lestensione .m indica che il file in questione contienecodice scritto in Objective-C e non in C standard). Aprite ancora un nuovo documento colvostro editor di testo preferito e digitate ancora quanto segue:

    #include #import "localita.h"#import

    #define kNuovaCitta 1#define kInserisciValori 2

    #define kMostraRisultati 3#define kEsci 0

    BOOL gDone=NO;NSMutableArray *citta;NSAutoreleasePool *ap;

    void MenuPrincipale(void);void NuovaCitta(void);void InserisciValori(void);void MostraRisultati(void);void Esci(void);

  • 7/30/2019 Tutorial Objective-C.pdf

    12/46

    12

    int main(void){

    ap=[[NSAutoreleasePool alloc] init];citta=[[NSMutableArray alloc] initWithCapacity:1];do

    { MenuPrincipale();} while(gDone==NO);return 0;

    }

    void MenuPrincipale(void){

    int scelta;

    printf("Menu principale:\n\n");printf("1. Nuova citta'\n");

    printf("2. Inserisci nuovi valori\n");printf("3. Mostra i risultati\n");printf("\n");printf("0. Esci\n");printf("\n");printf("Inserisci la tua scelta: ");scanf("%d",&scelta);

    switch(scelta){

    case kNuovaCitta:

    NuovaCitta();break;case kInserisciValori:

    InserisciValori();break;

    case kMostraRisultati:MostraRisultati();break;

    case kEsci:Esci();break;

    default:

    printf("Inserisci un numero compreso tra 0 e 3!\n\n");break;

    }}

    void NuovaCitta(void){

    NSString *nome;char *nomeC;

    nomeC=calloc(40,sizeof(char));

    printf("\n");

  • 7/30/2019 Tutorial Objective-C.pdf

    13/46

    13

    printf("Inserisci un nome per la nuova citta': ");scanf("%s",nomeC);nome=[[NSString alloc] initWithCString:nomeC];[citta addObject:[[localita alloc] initWithName:nome]];[nome release];

    free(nomeC);}

    void InserisciValori(void){

    NSEnumerator *en;id obj;

    en=[citta objectEnumerator];while(obj=[en nextObject])

    [obj nuoviValori];}

    void MostraRisultati(void){

    NSEnumerator *en;id obj;

    en=[citta objectEnumerator];while(obj=[en nextObject])

    [obj mostraValori];}

    void Esci(void){gDone=YES;[citta release];[ap release];

    }

    Salvatelo come main.m. Ora giunto il momento di compilare il nostro programma.Siccome la prima volta che facciamo una cosa del genere in Objective-C, vediamo la cosa neldettaglio col nostro primo box di approfondimento:

    Compilare ed eseguire programmi Objective-CAprite lapplicazione Terminal, la trovate nella cartella Utilities dentro la cartellaApplicazioni. Vi comparir una scritta del tipo:

    [computer ~] utente%oppurecomputer:~ utente$dove al posto di computer ci sar il nome del vostro computer e al posto di utente

    ci sar il nome dellutente col quale avete fatto il login (probabilmente il vostro nome ocognome). Il segno ~ indica dove vi trovate nellalbero delle cartelle sullhard disk,ovvero nella vostra home folder (/Users/vostronome/).

    Andate nella cartella in cui avete salvato i file dellEsempio1: assumendo che abbiatecreato una cartella EsempiObjective-C allinterno della vostra home folder, digitate:

    cd EsempiObjective-C

  • 7/30/2019 Tutorial Objective-C.pdf

    14/46

    14

    e premete invio oppure return. La scritta diventer allora[computer ~/EsempiObjective-C] utente%oppurecomputer:~/EsempiObjective-C utente$Digitatels

    per vedere che cosa c allinterno della cartella. I file localita.h, localita.m e main.mdovrebbero essere visibili.

    Ora dobbiamo compilarli, ovvero trasformarli in qualche cosa di eseguibile dalcomputer. Per, rispetto a quando lavoravamo sul C nella prima puntata di questa trilogia,ora il nostro programma fatto da tre file, di cui due di codice e uno di header. Laprocedura di compilazione, pertanto, sar un po pi complicata.

    Iniziamo a trasformare in file oggetto il file localita.m. Un file oggetto la versionecompilatama non ancora eseguibile di un file di codice; compilata, perch il compilatoreha trasformato il listato (scritto in Objective-C nel nostro caso) in una sequenza diistruzioni comprensibili al microprocessore, ma non ancora eseguibile, perch ancoramanca il linking, ovvero quelloperazione che consente al programma di poter utilizzare lelibrerie messe a disposizione dal sistema operativo. Virtualmente, ogni programma ha

    bisogno di essere linkato rispetto a qualche libreria. Il compilatore gcc esegueautomaticamente loperazione di linking, a meno che non gli venga detto esplicitamente dinon farla. Noi ora gli diremo di non farla, perch il nostro programma costituito da duefile di codice, che pr ima dovremo compilare, e solo successivamente linkeremotrasformandoli nel programma vero e proprio eseguibile dallutente.

    Iniziamo pertanto digitando, sul terminale, il comando:gcc -c localita.me premete invio o return alla fine. Lopzione -c indica proprio che volete compilare

    ma non linkare il file sorgente. Se non avete fatto errori, dopo pochi istanti il compilatoreavr finito. Digitate

    lse vedrete che, oltre ai file precedenti, se n aggiunto un altro, localita.o, il file

    oggetto di localita.m; da notare che con questa operazione abbiamo gi lavorato anche sulocalita.h, in quanto si tratta del file di header di localita.m, e pertanto statoautomaticamente preso in considerazione.

    Ora compiliamo il file main.m:gcc -c main.me il file main.o verr creato. Ora linkiamo il tutto:gcc -o Esempio1 localita.o main.o -lobjc -framework CocoaCon questo comando abbiamo creato il file eseguibile Esempio1 (lopzione -o),

    mettendo insieme i file localita.o e main.o creati prima, utilizzando la libreria Objective-C(lopzione -lobjc) e ilframework(che poi una specie di libreria) Cocoa, cosa che rendequesto programma linkabile ed eseguibile solo con MacOS X, e non con altri sistemioperativi per i quali sia disponibile lObjective-C (lopzione -framework Cocoa); la

    ragione di questa scelta che Cocoa ci mette a disposizione alcune classi usabili conObjective-C che ci tornano utilissime per i nostri programmi.Ora possiamo finalmente eseguire il programma digitando:./Esempio1e, come al solito, premendo invio o return alla fine.

    Giocate un po con il programma, inserendo dapprima due o tre nomi di citt, poiinserendo qualche valore di temperatura ed umidit. Notate che potete aggiungere nuove cittanche quando avete gi inserito dei valori per quelle immesse in precedenza. Visualizzatetemperature e umidit medie per tutte le vostre localit. Poi, quando vi siete stufati e siete ansiosidi capire come funziona questo gioiellino, proseguite nella lettura.

  • 7/30/2019 Tutorial Objective-C.pdf

    15/46

    15

    Interfaccia di una classe

    Iniziamo con lesaminare il file localita.h. Come lestensione suggerisce, un file diheader, quindi ci aspettiamo di trovare definizioni, costanti, le dichiarazioni delle variabili chesaranno globali per i file di codice che faranno uso di questo file di header, prototipi di

    funzioni. Tuttavia, essendo questo file associato ad un file di codice che contiene una classeObjective-C, lo chiameremo pi propriamente un file di interfaccia, o, pi semplicemente,linterfaccia della classe.

    Essa incomincia subito con un comando nuovo, #import : ilcomando #import non molto diverso da #include, anzi, meglio, perch assicura che il filedi header specificato sia importato solo se necessario, ovvero solo se non ancora statoimportato in precedenza. Infatti, in questo programma le cose sono piuttosto semplici, ma inprogrammi pi complessi, dove i file di header vengono letti e inclusi in molti file di codice, facile cercare di includere pi volte lo stesso file di header; il comando #include, in questicasi, genera degli errori in fase di compilazione. Il comando #import, invece, disponibile soloin Objective-C, risolve questo problema. Il file di header che viene importato Cocoa.h: essocontiene tutte le definizioni e i prototipi necessari per usare allinterno del nostro programma leclassi Cocoa, sviluppate da Apple per rendere pi agevole la programmazione di MacOS X.

    Dopo un paio di definizioni, la direttiva@interface localita : NSObject segnalache iniziata la vera e propria interfaccia della classe. La classe di cui stiamo scrivendolinterfaccia si chiama localita, e sar utilizzata nel nostro programma per tenere traccia ditutte le propriet di ogni localit, ovvero nome, temperatura ed umidit medie, ecc. Accanto alnome della classe, dopo il due punti, segnata la classe genitrice, ovvero la classe da cuilocalita eredita: nel nostro caso NSObject (si dice che localita una sottoclasse diNSObject). Ci occuperemo nel capitolo 4 di ereditariet e sottoclassi. Per ora basti sapere che, ameno di esigenze specifiche, una classe deve sempre ereditare da NSObjecto da una suasottoclasse. A differenza del C++, Objective-C non permette di definire pi di una classegenitrice.

    Tra parentesi graffe sono indicate tutte le variabili di cui la classe localita avr bisogno:

    due variabili di tipo double memorizzeranno la temperatura media e lumidit media,rispettivamente, della localit il cui nome memorizzato in una variabile di tipo puntatore aNSString: si tratta di una classe definita allinterno del framework Cocoa (ecco perch abbiamoimportato Cocoa.h allinizio) che permette di gestire stringhe di caratteri in modo molto pisemplice e potente che non con gli strumenti standard messi a disposizione del C edellObjective-C. Tecnicamente, qui non che ci serva pi di tanto usare la classe NSString,avremmo potuto tranquillamente lavorare con un puntatore a char o unarray di char, ma lusodella classe NSString era didattico! Che sia una classe definita allinterno del framework Cocoa indicato anche dal suo nome: tutte le classi il cui nome inizia con NS (che sta per NextStep, ilsistema operativo sviluppato dalla Next di Steve Jobs e da cui derivato MacOS X) sonodefinite allinterno del framework Cocoa (che, di fatto, si compone di due altri framework,lApplication Kite il Foundation Kit). Una regola generale che in Objective-C un oggetto va

    sempre identificato come un puntatore alla classe di appartenenza; non possibile, esplicitamente vietato creare oggetti staticamente (come invece si pu fare in C++). Quindi, lavariabile (abituiamoci a dire loggetto) nome sar di tipo NSString *. Infine, una variabile ditipo int terr memoria di quanti giorni siano gi stati conteggiati nel computo delle medie.

    Cos come sono state dichiarate, le variabili sono visibili (ovvero leggibli e scrivibili) soloallinterno degli oggetti appartenenti alla classe (e alle sue sottoclassi, come vedremo nelcapitolo 4); nessuna porzione di codice esterno alla classe potr accedere a queste variabili. possibile variare queste impostazioni, come vedremo sempre nel capitolo 4.

    Dopo la parentesi graffa di chiusura sono indicati i prototipi dei metodi della classe,ovvero di quelle funzioni che avranno il compito di manipolare i dati memorizzati nelle variabilidella classe ed eventualmente di comunicare con lesterno. La sintassi con cui si dichiarano iprototipi diversa da quella del C: un segno meno posto allinizio obbligatorio ed indica chesi tratta, per lappunto, di un metodo che potr essere chiamato da ogni oggetto della classe. Diper s la cosa potrebbe sembrare ovvia, ma c una sottigliezza: esistono dei casi in cui avete

  • 7/30/2019 Tutorial Objective-C.pdf

    16/46

    16

    bisogno di chiamare un metodo di una classe quando ancora loggetto appartenente a quellaclasse non esiste; tipicamente questa situazione si realizza quando state per creare un oggettoappartenente ad una classe ma, prima di costruirlo, avete bisogno di riservare (allocare) unquantitativo sufficiente di memoria; compito della classe, e non delloggetto (che non esisteancora), effettuare queste operazioni, mediante opportuni metodi, detti metodi di classe , chesono identificati (nel prototipo) da un simbolo + anzich -. Potrebbe venirvi a questo punto il

    dubbio che anche nel nostro caso sia necessario implementare un metodo di classe che si occupidi gestire la memoria necessaria per creare gli oggetti di cui avremo bisogno (le varie localit dicui terremo memoria dei dati meteorologici principali). vero, dobbiamo fare tutto questo. Ilfatto per di aver dichiarato la classe localita come sottoclasse di NSObject ci semplificherenormemente la vita (come vedremo tra non molto), al punto che non abbiamo nemmenobisogno di mettere, tra i prototipi, nessun metodo di classe.

    A seguire il segno meno troviamo, tra parentesi tonde, il tipo restituito dal metodo, chepu essere qualsiasi tipo di variabile valido in C ed Objective-C. Anche qui abbiamo il fatto cheun metodo, nome, restituisce un oggetto puntatore a NSString (guarda caso il nome dellalocalit), mentre un altro, initWithName:, restituisce un oggetto di tipo id: in Objective-C, id un tipo speciale, che identifica un oggetto appartenente a qualsivoglia classe, purch siasottoclasse di NSObject. un esempio dellassegnazione dinamica di tipo di cui dissertavamo

    nei capitoli precedenti: in Objective-C possiamo riferirci a delle variabili (a degli oggetti) senzaspecificare in anticipo di che tipo saranno; si pu usare un tipo generico, id, e poi, in fase diesecuzione, quel che sar sar. Bench nulla vieti, in questo esempio, di specificare che ilmetodoinitWithName: restituisce un oggetto di tipo (classe) localita (perch cos ),lasciamo, come convenzione fare, il tipo a id, perch ci torner immensamente utile nelprossimo capitolo, quando creeremo una sottoclasse di localita. Tra laltro, avete notato chequando parlo del metodo initWithName: includo sempre i due punti? Questo perch il metodoin questione richiede un argomento, e pertanto i due puntifanno parte integrante del nome delmetodo. Ometterli costituirebbe un errore di sintassi.

    Se un metodo, cos come viene dichiarato nellinterfaccia della classe, non ha un tipoesplicitamente assegnato, si assume che restituisca id. La dichiarazione dellinterfaccia di unaclasse termina con la direttiva @end, senza nessun punto e virgola alla fine.

    Riassumendo: in un file di interfaccia importiamo innanzitutto tutte le header che ciservono (tipicamente quelle riguardanti il framework Cocoa) perch le variabili e i metodi dellaclasse siano ben definiti. Poi definiamo tutte le costanti che ci servono. Quindi specifichiamo dachi eredita la nostra classe, avendo cura che si tratti sempre di NSObject o di una suasottoclasse (per comodit, come vedremo tra poco). Tra parentesi graffe dichiariamo quindi tuttele variabili di cui far uso la nostra classe, variabili che sono utilizzabili solo dagli oggetti dellaclasse e da nessun altro. Per sovrascrivere questo comportamento, dobbiamo aspettare ilprossimo capitolo (dove capiremo anche quando e perch convenga farlo). Dopo ladichiarazione delle variabili, e al di fuori delle parentesi graffe, dichiariamo i metodiimplementati dalla classe, preceduti dal segno meno e specificando tra parentesi tonde il tipodi ognuno di essi. Bench, formalmente, tutti i metodi siano uguali, alcuni di essi saranno daconsiderare privati ed altri metodi accessori, il cui significato e uso sar discusso tra poco.

    Siete un po spaventati da tutte queste novit? Avete le idee confuse? Ci sono un sacco dicose che state accettando per fede perch non ne vedete, ora come ora, la necessit o lutilit?Non preoccupatevi, normale. Adesso che disquisiremo un po dellimplementazione dellaclasse localita un po di cose inizieranno a diventarvi pi chiare. Col prossimo capitolo, poi,altre cose si inquadreranno meglio e troveranno un posto nel ricco e variopinto mondodellObjective-C.

    Implementazione di una classe

    Se il file di interfaccia una dichiarazione di intenti (la mia classe user queste variabili eimplementer questi metodi), il file di implementazione quello in cui si fa il lavoro sporco,quello in cui il codice Objective-C vero e proprio viene scritto. Nel nostro esempio, se localita.hera il file di interfaccia, naturale pensare che localita.m sia il file di implementazione; e in effetti

    cos. Vediamo com fatto.

  • 7/30/2019 Tutorial Objective-C.pdf

    17/46

    17

    Innanzitutto si importa sempre il file di header che contiene linterfaccia della classe. Secos non si facesse, sarebbe inutile creare il file di interfaccia. In effetti, tutto quanto contenutonel file di interfaccia potrebbe essere copiato e incollato allinizio del file di implementazione,evitando cos di avere a che fare con due file distinti. Tuttavia, tenere linterfaccia elimplementazione separate ha dei vantaggi: potete importare il file di interfaccia di una certaclasse in tutti i file di implementazione di classi che lavorano con oggetti appartenenti a quella

    classe; e poi, potete usare i file di interfaccia come documentazione: tutto ci che c da saperesu questa classe (che cosa fa) senza aver bisogno di andare a guardare limplementazione vera epropria (come lo fa).

    Limplementazione vera e propria inizia con la direttiva @implementation seguita dalnome della classe. necessario specificare questultimo perch un file potrebbe contenere leimplementazioni di pi classi, anche se non prassi comune farlo (anzi, sconsigliato). Quindi,i metodi che la classe deve implementare sono riportati col loro prototipo, ma, al posto del puntoe virgola finale, con la solita famosa coppia di parentesi graffe allinterno della quale si trovanole istruzioni che il programma eseguir quando il metodo verr chiamato. Come vedete, i metodinon sono poi molto diversi dalle funzioni. Lordine con cui compaiono i metodi non essenziale, purch compaiano tutti quelli che avete dichiarato nel file di interfaccia. In realt nepossono comparire anche di pi (con alcune regole, ovviamente), come vedremo tra un po.

    Quando avete finito di scrivere il codice per tutti i metodi, la direttiva @end, sempre senza puntoe virgola finale, chiude limplementazione della classe.Onde evitare di dimenticare pezzi per strada, io ho labitudine di implementare i metodi

    nello stesso ordine con cui li dichiaro, a cominciare dal metodo di inizializzazione. Ogni classedeve averne almeno uno, deve restituire un tipo id e il suo nome deve essere init o per lomeno deve iniziare con init ( una convenzione, non un obbligo, ma meglio seguirla).Compito di ogni metodo di inizializzazione affrontare nella maniera pi semplice possibilequelle questioni barbose e complicate riguardanti lallocazione della memoria che servealloggetto che sta per essere creato ed assegnare alle sue variabili dei valori iniziali.

    Nel nostro caso, ogni volta che vogliamo monitorare temperatura e umidit medie di unacerta localit, iniziamo a dare un nome alla localit stessa; il nostro programma, allora, chiameril metodo initWithName: della classe localita fornendogli, come argomento, un oggetto di

    tipo NSString contenente il nome della localit scelta dallutente. Il metodo di inizializzazioneinizia in maniera standard (ovvero: tutti i metodi di inizializzazione devono iniziare cos), conlistruzione [super init] . Mamma mia! Qui succedono gi un sacco di cose! Vediamole unaper una.

    Innanzitutto siamo di fronte ad un nuovo costrutto sintattico, ovvero il modo cheObjective-C ha per chiamare i metodi di un oggetto: il nome delloggetto che si vuole usarecompare per primo tra parentesi quadre, seguito, sempre tra le parentesi, dal nome del metododa chiamare. Sostanzialmente, come prima cosa, il nostro metodo di inizializzazione sta dicendoalloggettosuper di eseguire il suo metodo denominato init. Loggetto super definitoallinterno di ogni classe come loggetto che punta (ricordatevi che in Objective-C gli oggettisono sempre puntatori!) alla superclasse o alla classe genitrice della classe di cui statescrivendo limplementazione; poich localita una sottoclasse di NSObject, super punta ad

    un oggetto di classe NSObject. E qui sta la grande comodit di Cocoa: come si fa a riservare(allocare) memoria sufficiente per farci stare loggetto di classe localita che lutente vuolecreare? Quanta memoria serve? Come facciamo ad essere sicuri che ce ne sia abbastanza? Comefacciamo a trovarla? E chi se ne frega? Laver dichiarato la classe localita come sottoclasse diNSObject ci permette di risolvere tutti questi problemi in un colpo solo, semplicementescrivendo[super init]; loggetto di classe NSObject a cui punta super penser a tuttoquesto per noi. Comodo, invero! Non dimenticate mai di scrivere questa istruzione per prima.Se non lo fate, il vostro programma far cose veramente molto strane, e sar molto difficilecapire perch.

    Segue unistruzione il cui compito quello di memorizzare nella variabile nome il nome(ma guarda un po) della localit scelta dallutente. Poich nome un oggetto di tipo NSString,dobbiamo innanzitutto eseguire il metodo di classealloc della classe NSString. alloc un

    metodo implementato da NSObject e, automaticamente, da tutte le sue sottoclassi (come

  • 7/30/2019 Tutorial Objective-C.pdf

    18/46

    18

    localita e NSString) che si occupa di riservare memoria sufficiente alla creazione di unoggetto, prima, ovviamente, che questo venga creato (ecco perch un metodo di classe).Essendo un metodo di classe, tra parentesi quadre non dobbiamo mettere il nome di un oggetto,ma il nome di una classe. [NSString alloc], allora, vuol dire che vogliamo creare un oggettodi tipo NSString. Il metodo alloc restituisce sempre un tipo id, quindi un oggetto, che nelcaso specifico appartiene alla classe specificata (NSString nel nostro caso). Tale oggetto allocato (abbiamo preparato la memoria necessaria col metodo alloc), ma non inizializzato(non abbiamo suddiviso la memoria tra le varie variabili che verranno utilizzate). La differenza la stessa che c tra il destinare un certo appezzamento di terra alla costruzione di una villettacon giardino (allocazione) e il progetto effettivo della villetta e del giardino (inizializzazione). Lavilletta e il giardino veri e propri saranno loggetto, che potr essere usato solo dopo chelallocazione sar stata fatta (il comune, ovvero il Sistema Operativo, ci ha dato la propriet delterreno e ci ha dato la licenza edilizia) e linizializzazione sar stata completata (il progetto dellavilletta e del giardino).

    Il metodo alloc, dicevamo, restituisce un oggetto; non che ce ne facciamo molto diquesto oggetto, dal momento che, non essendo ancora inizializzato, non utilizzabile. E quiviene una delle grandi comodit dellObjective-C: la sua sintassi che io definisco a scatole: lascatola[NSString alloc] un oggetto ancora non inizializzato; devo inizializzarlo. Potreiassegnare tale oggetto ad una variabile (mioOggetto), e poi inizializzarlo in seguito([mioOggetto initWithString:]), ma sarebbe uno spreco di variabili e di tempo. Moltomeglio inscatolare loggetto [NSString alloc] nella scatola che conterr listruzione diinizializzazione delloggetto stesso, ovvero [[NSString alloc] initWithString:] . Ilmetodo initWithString:, come tutti i medoti inizializzatori, restituisce un tipo id, quindi unoggetto. Esso proprio loggetto nome, ovvero la NSString contenente il nome della localitscelta dallutente e che dovremo tenere in memoria.

    Quando create un nuovo oggetto in un metodo di inizializzazione bene che siateconsapevoli che MacOS X richiede che seguiate alcune regole di base per non sprecarememoria (e per non rischiare di mandare in crash la vostra applicazione a causa di un noncorretto uso della stessa). Queste regole, che a voler approfondire un po la cosa possonodiventare molto complicate, vanno scrupolosamente seguite; io stesso ho penato un po perimpararle e soprattutto per convincermi che fosse veramente importante seguirle. Devo infinitiringraziamenti a Gian Luca Cannata e alla sua pazienza e competenza per avermi iniziato almondo della gestione della memoria in Objective-C e avermi fatto capire l dove sbagliavo(pressocch ovunque). Dunque, bench la gestione avanzata della memoria non faccia partedegli scopi di questo manuale, qui vediamo giusto i fondamenti necessari per poter proseguirecon tranquillit: quando creiamo un oggetto con la sintassi [[NomeClasse alloc] init] ocon qualche variante del genere, stiamo sostanzialmente dicendo al sistema operativo diriservare spazio in memoria per loggetto che ci interessa e di tenerlo l per il futuro, perch ciservir. Nel nostro caso, loggetto nome verr accuratamente conservato nella memoria delprogramma. Ma se, un bel giorno, non ne avremo pi bisogno, il sistema operativo ci chiede difarglielo sapere. Il modo giusto per farlo trasmettere alloggetto in questione il messaggio(metodo) release, come vedremo fra poco.

    Incidentalmente, stiamo apprezzando il fatto che qualunque oggetto sia in una maniera onellaltra figlio di NSObject: i metodi alloc e release e vari altri sono implementati dallaclasse NSObject, quindi da Cocoa, quindi da MacOS X, e li possiamo usare con qualunqueoggetto, ivi compresi quelli che creiamo noi (come gli oggetti appartenenti alla classelocalita), senza alcun bisogno di implementarli esplicitamente: infatti, quando di un oggetto siinvoca un metodo che non stato scritto esplicitamente nella sua classe, Objective-C risale allaclasse genitrice e le chiede di eseguire il metodo in questione; se questa non sa che farsene, ilmetodo viene passato alla classe genitrice della classe genitrice delloggetto originale (la classenonna?), e cos via, fino a che qualcuno, fosse anche NSObject, non ha a disposizione il codicevero e proprio che implementa questo metodo. Naturalmente, se voi chiamate il metodocicciopirillo e poi non lo implementate, molto probabile che nessuno, nemmenoNSObject, lo implementi; in questo caso, la chiamata al metodo viene lasciata cadere, e il vostroprogramma genera un messaggio di avviso che viene memorizzato nel file console.log.

  • 7/30/2019 Tutorial Objective-C.pdf

    19/46

    19

    Nel metodo initWithName: ci sono poi semplici istruzioni di assegnazione a variabile: lalocalit appena stata creata, quindi le grandezze temperaturaMedia, umiditaMedia enumeroGiorni sono pari a zero.

    Per finire, siccome tutti i metodi di inizializzazione devono restituire un oggetto (s stessi),scriviamoreturn self ; self non nientaltro che un puntatore alloggetto stesso. Ogni

    oggetto sa chi , gli basta guardare nella variabile predefinitaself

    .Il secondo metodo che troviamo nel file localita.m dealloc: come avrete notato, esso

    non presente tra i prototipi nel file di interfaccia. Come mai? Il metodo dealloc implementato dalla classe NSObject e quindi da tutte le sue sottoclassi (come localita). Se,per qualche ragione, una sottoclasse vuole sovracrivere il metodo dealloc di NSObject perestenderne le funzionalit, basta che lo inserisca nel proprio file di implementazione, senzabisogno di metterlo anche nel file di interfaccia; se, comunque, nel dubbio lo mettete anche l,male non fa. E quali sarebbero queste ragioni per cui una sottoclasse di NSObject vorrebbesovrascirivere il metodo dealloc? Una, soprattutto: se nel metodo di inizializzazione(initWithName: nel nostro caso) avete creato uno o pi oggetti con una chiamata al metodoalloc di una classe e poi ad un qualche metodo di inizializzazione, allora sovrascrivere il

    metodo dealloc obbligatorio; in esso, dovete liberare la memoria occupata da tutti gli oggettiche avete creato con una chiamata ai rispettivi metodi release. esattamente quello che facciamo qua: loggetto nome era stato creato con la chiamata

    [[NSString alloc] initWithString:] nel metodo initWithName:; ora lo rilasciamo conla chiamata [nome release]. Poi, siccome il metodo dealloc di NSObject si occupa di unsacco di cose utilissime come ad esempio liberare la memoria che il nostro oggetto di classelocalita occupa (e che era stata allocata con la chiamata [super init] nel metodo diinizializzazione), non dimentichiamoci di invocare ancora il metodo dealloc della classegenitrice, mediante [super dealloc]; questa deve essere lultima cosa che facciamo nelmetodo dealloc delle nostre classi.

    Il metodo successivo, dal prototipo (NSString *)nome, un cosiddetto metodoaccessorio o metodo di accesso . Che cosa faccia abbastanza evidente: quando chiamato,restituisce il contenuto della variabile nome, ovvero il nome della localit. In realt, nel nostroprogramma non useremo mai questo metodo, nel senso che nel file main.m, che si occuper diorganizzare tutto il lavoro, da nessuna parte il metodo nome verr chiamato. E allora perch lomettiamo? Per questioni di eleganza!

    Come abbiamo gi detto, le variabili di un oggetto non sono accessibili al codice esterno aquello di implementazione delloggetto stesso, a meno di introdurre speciali direttive chevedremo nel prossimo capitolo. In generale, introdurre queste direttive non comunque unabuona idea: proteggere le variabili di un oggetto dallaccesso esterno infatti una strategiaparticolarmente importante. Se il codice esterno allimplementazione di un oggetto non sa diquali variabili abbia bisogno loggetto stesso, non pu, n intenzionalmente n accidentalmente,modificarle, causando comportamenti strani o addirittura crash del programma. importante

    che lunico modo per modificare le variabili di cui fa uso un oggetto sia attraverso i metodiaccessori, ovvero metodi che permettono di scrivere o di leggere il contenuto di una o pivariabili delloggetto stesso. Pu sembrare una complicazione, ma non lo . Il nostroprogramma scrive nella variabile nome delloggetto di classe localita mediante il metodoinitWithName:; noi, che abbiamo fatto tutto il programma, sappiamo che la variabile nome un oggetto di classe NSString; avremmo potuto rendere questa variabile pubblica e lasciare cheil programma, in un preciso punto del file main.m, scrivesse direttamente allinterno di questavariabile anzich chiamare il metodo initWithName:; solo che un bel giorno decidiamo dimodificare la classe localita, perch ci viene pi comodo memorizzare il nome della localitin maniera diversa. Se lasciamo che il programma acceda alla variabile nome direttamente,dobbiamo modificarlo in tutti quei punti in cui la variabile nome viene letta o scritta, epotrebbero essere tanti. Se invece la variabile nome viene scritta solo col metodo diinizializzazione (o con un metodo di accesso in scrittura) e viene letta solo col metodo di

  • 7/30/2019 Tutorial Objective-C.pdf

    20/46

    20

    accesso in lettura, tutto molto pi facile: possiamo anche sbarazzarci della variabile nome,basta che lasciamo i due metodi di accesso: ci penseranno loro a memorizzare il nome inmaniera opportuna, dal punto di vista del mondo esterno il nome della localit verr sempre lettoe scritto nello stesso modo. Questo, se gi fa comodo a noi, di importanza fondamentalequando si lavora ad un progetto a molte mani: si definiscono le interfacce delle classi,soprattutto si specificano bene quali sono i metodi di accesso di una classe, cos che tutti i

    programmatori sappiano come usare quella classe; chi si occuper di implementarlaeffettivamente potr fare quello che vuole, modificarla anche cento volte; ma se i metodi diaccesso non cambiano, saranno fatti suoi, il lavoro degli altri programmatori sar salvo. Sularga scala, questo torna molto utile anche a noi: tutte le classi del framework Cocoa (finoraabbiamo accennato a NSObject ed NSString, tanto per fare degli esempi), possono esseremodificate da Apple in qualunque momento con qualunque aggiornamento del sistemaoperativo. Questo per non vuol dire che tutti i programmi gi realizzati smetteranno difunzionare: basta che i metodi di accesso siano sempre gli stessi! Grande cosa, laprogrammazione ad oggetti!

    Unultima nota: il metodo nome ha lo stesso nome (eh! brutta cosa, i nomi uguali) dellavariabile nome: non c conflitto, lo spazio dei nomi dei metodi e delle variabili separato; anzi, prassi comune far s che il metodo di accesso in lettura di una variabile abbia lo stesso nome

    della variabile stessa. buona norma avere un metodo di accesso in lettura per lo meno di ognivariabile per la quale esiste un metodo di accesso in scrittura, fosse anche il metodo diinizializzazione; pertanto, anche se non lo useremo, inseriamo il metodo nome nellinterfaccia enellimplementazione della nostra classe localita.

    Il metodo successivo, nuoviValori, , in senso lato, un metodo di accesso in scrittura;teoricamente, un metodo di accesso in scrittura accetta i valori da assegnare alle variabili di cuisi occupa come argomenti; teoricamente. Non dico che questa non sia una buona prassi, ma non sempre la scelta migliore. Nel nostro caso, e vedremo nel prossimo capitolo come la sceltafatta sia stata quanto mai opportuna, abbiamo bisogno di permettere allutente di inserire, perogni localit, temperatura e umidit a mezzogiorno, al fine di calcolare le medie. Chi si deveoccupare di questa operazione? Un buon candidato potrebbe essere il file main.m, il cuore del

    programma, colui che organizza tutto; ma se un giorno decidiamo che, oltre a temperatura eumidit, il nostro programma deve memorizzare anche la pressione atmosferica, siamo costrettia modificare la classe localita per tenere conto di queste modifiche e anche il file main.m,ovvero il cuore del nostro programma, perch dovr chiedere allutente di inserire un valore inpi. Se invece affidiamo alla classe localita il compito di richiedere allutente i valori dainserire, possiamo estendere alla pressione atmosferica le grandezze interessanti da monitorare,e il file main.m non avr mai bisogno di saperlo: infatti, il solo metodo nuoviValori sarcoinvolto, oltre allimplementazione della classe localita, naturalmente. Questo fa moltoprogrammazione ad oggetti: gli oggetti di classe localitasanno di quali valori hannobisogno per effettuare i loro conti; non compito di main.m manipolare i dati memorizzati neglioggetti; compito di main.m chiedere agli oggetti di manipolarseli loro, i loro dati, nella manierache ritengono pi opportuna.

    nuoviValori, pertanto, non fa niente di particolarmente difficile: chiede allutente diinserire temperatura e umidit a mezzogiorno per loggetto in questione (una ben precisalocalit); lutente informato di quale localit si tratta grazie al fatto che unistruzione printf()stampa a schermo la variabile nome; tuttavia, printf() non in grado di gestire direttamentestringhe di testo appartenenti alla classe NSString; per fortuna, queste dispongono di unmetodo, cString, che le converte in un formato comprensibile alla funzione printf(). Infine,una volta che i nuovi valori di temperatura e di umidit sono stati inseriti, i metodinuovaTemperatura: e nuovaUmidita: vengono chiamati; essi sono ad esclusivo uso internodella classe localita, e hanno il compito di ricalcolare di volta in volta temperatura e umiditmedie, rispettivamente, aggiornando le rispettive variabili di tipo double dichiaratenellinterfaccia della classe. Nessuna di queste variabili dispone di metodi diretti di accesso inscrittura, quindi non dispone nemmeno di metodi di accesso diretto in lettura. Il loro metodo diaccesso indiretto in scrittura proprio nuoviValori, che infatti accompagnato da un

  • 7/30/2019 Tutorial Objective-C.pdf

    21/46

    21

    analogo metodo di accesso in lettura: mostraValori. Questo non fa altro che scrivere aschermo, mediante opportune funzioni printf(), il nome della localit e temperatura media eumidit media a mezzogiorno relative agli ultimi numeroGiorni giorni.

    Eseguire un programma

    Con lanalisi dei file localita.h e localita.m ci siamo concentrati su un aspetto moltospecifico, ma fondamentale, forse il pi importante di tutti: quali dati deve maneggiare il mioprogramma e come li manegger? In C, la domanda divisa in due parti: quali dati ha a chevedere con le variabili presumibilmente globali del programma; come ha a che vedere con lefunzioni del programma che manipoleranno queste variabili. In Objective-C (in qualunquelinguaggio ad oggetti, in realt), la domanda una sola, perch la classe che creiamo che sioccupa tanto della memorizzazione dei dati quanto della loro manipolazione. localita.h elocalita.m mostrano come fatto ogni oggetto di classe localita; il programma principale, cheper noi il file main.m, avr invece il compito di mettere da qualche parte linsieme degli oggettidi classe localita, che potranno essere uno o centomila (se fossero nessuno non avrebbemolto senso scrivere il programma), affinch ognuno di essi possa essere creato, distrutto emodificato a piacere dellutente secondo quanto previsto dallimplementazione della classe

    localita.Analizziamo allora il file main.m: esso inizia con lincludere la libreria stdio.h perch ciservir per usare le funzioni printf() e scanf(); quindi importa localita.h perch gli oggetticon cui avr a che fare apparterranno a questa classe; infine, importa Cocoa.h perch userclassi del framework Cocoa che ci torneranno particolarmente utili. Definisce quindi alcunecostanti, che ci serviranno per il menu principale che consentir allutente di creare nuove citt,inserire i valori di temperatura e umidit per le citt esistenti, e vedere i valori medi, oppureuscire dal programma. Quindi, dichiara le variabili globali: gDone, di tipo BOOL, una variabileche pu contenere solo uninformazione di tipo vero/falso (nella forma del numero intero 1 perindicare vero oppure s e del numero intero 0 per indicare falso oppure no). BOOL un tipo divariabile che non esiste in C e che stato aggiunto in Objective-C per comodit deiprogrammatori.

    La seconda variabile globale loggetto citta, un puntatore a NSMutableArray, unaclasse del framework Cocoa che consente di gestire in maniera estremamente versatile e potentedelle array di oggetti; occupandoci del C nella prima puntata di questa trilogia abbiamo parlatodi array, nella forma di array di numeri o di caratteri. Volendo, possiamo creare array distrutture; in Objective-C in accoppiata con Cocoa, possiamo creare array di oggetti! Per noi,citta sar loggetto nel quale memorizzeremo tutti gli oggetti di classe localita che lutentevorr creare per mantenere le statistiche di temperatura e umidit media.

    Infine, loggetto ap, di classe NSAutoreleasePool, una roba un po strana. Sepermettete, ce ne occupiamo tra poco.

    A seguire ci sono i prototipi delle funzioni che useremo nel file main.m: abbiamo omessoil prototipo della funzione main() , poich standard. Abbiamo poi la funzioneMenuPrincipale(), che mostra allutente il menu con tutte le opzioni tra cui pu scegliere, e

    una funzione per ognuna delle operazioni che lutente pu eseguire, ovvero creare una nuovacitt, inserire i valori di temperatura e umidit per le citt esistenti, visualizzare i valori medi, ouscire dal programma.

    La funzione main() inizia con unistruzione strana: ap=[[NSAutoreleasePool alloc]init]; che sar mai? Qui dovete avere un po di fede. Quando realizzate un programma inObjective-C che fa uso di oggetti appartenenti a classi Cocoa, ovvero quando importate il file diheader Cocoa.h e compliate linkando al framework Cocoa, MacOS X si aspetta che il vostroprogramma disponga di un autorelease pool , un costrutto necessario a Cocoa per gestire inmaniera efficiente la memoria che verr allocata e liberata tutte le volte che creerete, in manieraimplicita od esplicita, oggetti temporanei, che non devono sopravvivere a lungo, magariperch vi servono solo rapidamente allinterno di un metodo per trasferire un po diinformazioni o fare un po di conti. Se questo autorelease pool non disponibile, la memoriadestinata dal sistema al vostro programma si riempie presto di schifezze, rendendo molto

  • 7/30/2019 Tutorial Objective-C.pdf

    22/46

    22

    probabile che nel giro di poco tempo il programma vada in crash; inoltre, il programma viriempie di avvisi che dicono pressappoco che, non essendoci un autorelease pool disponibile, lamemoria sta diventando un ricettacolo di gente di malaffare. Per evitare di incorrere in questiinconvenienti, inseriamo questa istruzione allinizio della funzione main() (e dichiariamo lavariabile ap come puntatore a NSAutoreleasePool tra le variabili globali), cos MacOS X contento e non ci stressa.

    Quindi dobbiamo fare posto per le citt che lutente vorr creare; esse verrannoimmagazzinate nelloggetto citta di classe NSMutableArray, ovvero unarray mutevole,ovvero i cui contenuti possono essere modificati (Cocoa mette a disponizione anche una classeNSArray, genitrice di NSMutableArray, a cui appartengono array i cui contenuti non possonoessere modificati una volta che larray stata creata). Iniziamo a chiamare il metodo di classealloc con [NSMutableArray alloc] per riservare in memoria un appezzamento adatto adaccogliere un oggetto di classe NSMutableArray; quindi, con loggetto restituito,inscatoliamo il tutto con la chiamata ad uno dei vari metodi di inizializzazione della classeNSMutableArray: ho scelto il metodo initWithCapacity:, che inizia a riservare memoriasufficiente per archiviare tanti oggetti quanti sono indicati nellargomento (uno, nel nostroesempio), riservandosi poi di recuperare automaticamente memoria sufficiente qualora ilnumero di oggetti da memorizzare fosse maggiore. Loggetto allocato e opportunamenteinizializzato pu ora essere assegnato alla variabile citta, che ora unarray mutevole a tuttigli effetti, pronta ad archiviare al suo interno le varie localit che lutente vorr creare.

    Subito dopo, un ciclo do-while si occupa di continuare a mostrare il menu principaleallutente finch questi non sceglie di uscire dal programma: la variabile globale gDone, che pufar uscire da questo loop e subito dopo dalla funzione main() causando larresto delprogramma, viene impostata a YES solo se lutente sceglie di uscire.

    La funzione MenuPrincipale() molto semplice: una serie di funzioni printf() scrivea schermo le varie opzioni a disposizione dellutente, e una funzione scanf() accetta linputnumerico da tastiera; un blocco switch, sfruttando le costanti definite allinizio, verifica che lascelta dellutente sia tra quelle effettivamente disponibili e dirotta il flusso del programma allafunzione opportuna; in caso di scelta errara da parte dellutente (un numero fuori dai limiti

    consentiti), un messaggio derrore invita a riprovare, il flusso del programma torna al ciclo do-while della funzione main() ed essendo la variabile gDone ancora fissata a NO la funzioneMenuPrincipale() viene chiamata nuovamente.

    Quando lutente sceglie di creare una nuova citt (operazione che andrebbe fatta subito,visto che non ha molto senso inserire temperature e umidit per localit che non esistono) vienechiamata la funzione NuovaCitta(). Visto che siamo in vena di cose nuove, ho deciso diintrodurre una piccola aggiunta a quanto avevamo imparato con la nostra Introduzione allinguaggio C; allepoca, trattavamo le stringhe di caratteri come array di tipo char; adesso,anche in analogia col modo con cui trattiamo tutti gli oggetti, definiamo una stringa di carattericome un puntatore a char. Esattamente come nel caso degli oggetti, dobbiamo in qualchemaniera allocare memoria per questa stringa; non trattandosi di un oggetto non possiamo

    chiamare il suo metodo alloc, ma possiamo usare una delle funzioni standard del C,calloc(), il cui secondo argomento indica di che tipo sono le variabili per cui voglio creareposto in memoria, e il primo argomento indica quante sono; in altre parole, concalloc(40,sizeof(char)) stiamo riservando spazio in memoria per 40 caratteri, ovvero peruna stringa, nomeC, lunga fino a 40 caratteri. Lequivalente del metodo di inizializzazione perquesta stringa lassegnazione che viene fatta allinterno della funzione scanf().

    Fin qui tutto abbastanza pacifico. Ora avviene la prima delle cose spettacolari:finalmente usiamo la classe localita! Sfruttando il fatto che essa eredita il metodo alloc daNSObject, chiamiamo [localita alloc] per allocare la memoria necessaria, poi loggetto (ditipo id) restituito da questa chiamata lo passiamo subito (inscatolandolo) al metodo diinizializzazione della nostra classe, ovvero initWithName:. Esso vuole come argomento unoggetto di classe NSString, e non un semplice puntatore a char, ed ecco perch listruzione

    precedente alloca memoria per un oggetto di classe NSString inizializzandolo con un puntatore

  • 7/30/2019 Tutorial Objective-C.pdf

    23/46

    23

    a char (nomeC) ed assegnando loggetto risultante a nome. Quindi, [[localita alloc]initWithName:nome] crea un oggetto di classe localita con il nome scelto dallutente. Essova immagazzinato da qualche parte; lo mettiamo nellarray citta che abbiamo creatoappositamente, procedendo con linscatolamento: la classe NSMutableArray offre un metodo,addObject:, che aggiunge loggetto messo ad argomento (di classe id, quindi qualunque)

    nellarray: il nostro oggetto [[localita alloc] initWithName:nome] , quindi lo mettiamoad argomento di [citta addObject:]; ora citta contiene un oggetto nuovo, di classelocalita, inizializzato col nome scelto dallutente. Quindi liberiamo la memoria occupatadalloggettonome, visto che non ci serve pi, e quella occupata da nomeC, visto che anche leiormai diventata inutile.

    In realt avremmo potuto fare ancora meglio: loggetto nome di classe NSString dellafunzioneNuovaCitta() non serve a nulla: soltanto un posto in cui memorizzaretemporaneamente il nome della citt, convertito da char * a NSString, prima di memorizzarlodefinitivamente nelloggetto di classe localita. Infatti loggetto nome non ci servir al di fuoridella funzione NuovaCitta(), essendo in effetti una variabile locale per essa. Possiamo quindisbarazzarci delloggetto nome: [[localita alloc] initWithName:] vuole un oggettoNSString come argomento, proprio [[NSString alloc] initWithCString:nomeC];

    dobbiamo per stare attenti; se scrivessimo [[localita alloc] initWithName:[[NSStringalloc] initWithCString:nomeC]] ed assegnassimo il tutto ad un nuovo elemento dellarraycitta: [citta addObject: [[localita alloc] initWithName:[[NSString alloc]initWithCString:nomeC]]] commetteremmo un errore sottile: la sintassi [[NSStringalloc] initWithCString:] crea un oggetto (che non assegnamo a nessuna variabile) alquale non saremmo poi in grado di mandare un messaggio di release: un errore di gestionedella memoria, un memory leak. Ma Cocoa ci mette a disposizione unalternativa, un metodoper generare un oggetto temporaneo senza che dobbiamo preoccuparci di mandargli unrelease quando non ci occorre pi: usiamo pertanto la sintassi [NSStringstringWithCString:]. Come vedete facciamo uso di un metodo di classe (infatti chiamatonello stesso blocco di parentesi quadre in cui compare il nome della classe) che fa la stessa cosadelluso combinato dei metodi alloc e initWithCString:, ma genera un cosiddetto oggettoautorelease. Ricordate quando allinizio della funzione main() abbiamo creato un autoreleasepool? Beh, serve per gestire queste cose qua. Un oggetto autorelease un oggetto la cuimemoria verr automaticamente liberata dal sistema operativo al momento opportuno,sicuramente quando lesecuzione del programma sar uscita dal metodo o dalla funzione in cui stato creato loggetto autorelease. Ecco quindi una regola doro: se un oggetto vi serve in pipunti (funzioni o metodi) del programma, allocatelo con alloc e inizializzatelo con un metodoinit, e quando non vi serve pi rilasciatelo con release; se un oggetto ha un uso temporaneo,limitato ad una sola funzione o metodo, potete usare la sintassi che fa uso di alloc, init erelease, oppure usare un metodo autorelease (un metodo di classe che non sia alloc), cospotete inscatolare tutto in ununica espressione. Potenza dellObjective-C e della sua sintassicos diversa dal C++! Con una sola riga di istruzioni possiamo creare un oggetto (autorelease)

    NSString a partire da un puntatore a char, usarlo come inizializzatore di un oggetto di classelocalita creato alluopo e aggiungere tale oggetto, una volta inizializzato, ad un oggetto diclasseNSMutableArray! Da principio, quando non siete ancora abituati, tutto questoinscatolare fa girare un po la testa. Ma poi ci si fa labitudine, e ci si chiede com statopossibile vivere senza la sintassi dellObjective-C per tutto il tempo che si usato il C perprogrammare! Vediamo di riassumere tutto con uno schemino:

    Allinterno della funzione InserisciValori() succedono altre cose spettacolari.Compito di questa funzione , per ogni oggetto (quindi per ogni localit) memorizzato nellarray

  • 7/30/2019 Tutorial Objective-C.pdf

    24/46

    24

    citta, chiamare il suo metodo nuoviValori, implementato nel file localita.m. Ci sono pimodi per scorrere i vari oggetti presenti in unarray, il pi ovvio dei quali realizzare un ciclofor la cui variabile di controllo fa da indice per le varie caselle dellarray. Questo si pu fareanche con oggetti di classe NSArray e NSMutableArray (anche se in modo un po diversorispetto alle array standard del C), ma esse ci mettono a disposizione un modo molto pielegante, mediante lutilizzo di un oggetto di classe NSEnumerator (un enumeratore,ammesso che esista questa parola in italiano). Notate la bellezza: il metodo objectEnumeratorimplementato dalla classe NSArray (e quindi automaticamente dalla sua classe figlia osottoclasseNSMutableArray) restituisce un oggetto di classe NSEnumerator, gi allocato einizializzato (autorelease), che assegnamo alla variabile en. La classe NSEnumerator, a suavolta, implementa un metodo, nextObject, che restituisce il prossimo oggetto della lista. Chevuol dire? Applicato ad unarray significa che loggetto en contiene un elenco di tutti gli oggettimemorizzati allinterno dellarray stessa, e tutte le volte che chiamo il metodo nextObjectdelloggetto di classe NSEnumerator mi viene restituito uno degli oggetti presenti nellarray;uno per volta, finch non li esaurisco; a questo punto, [en nextObject] restituisce nil, lacondizione nel ciclo while diventa falsa e si esce dal ciclo. Loggetto restituito da [ennextObject] va memorizzato da qualche parte, ad esempio nella variabile che ho chiamato

    obj; non devo preoccuparmi di chiamare il metodo release di obj, perch [en nextObject]restituisce un oggetto autorelease (mica ho usato i metodi alloc ed init per crearlo, no?). Ora,io so che tutti gli oggetti contenuti nellarray citta sono di classe localita, quindi potreidichiarareobj come puntatore alla classe localita, ma preferisco sfruttare le propriet diassegnazione dinamica di tipo dellObjective-C (una cosa che in C e in C++ non posso fare!),lasciando obj dichiarato come di tipo generico id, ovvero un oggetto qualunque. Questo perchsono previdente: in una NSMutableArray come citta posso inserire oggetti appartenenti aclassi diverse (ecco un indubbio vantaggio rispetto alle array del C, che invece immagazzinanosolo variabili dello stesso tipo); sfogliando gli oggetti uno per uno mediante luso della classeNSEnumerator e assegnandoli ad obj, se lo costringessi ad essere un puntatore a localitaotterrei un errore (in fase di esecuzione del programma) se uno degli oggetti non fosse di classelocalita. Al momento non possibile, ma in futuro? Se il Servizio Meteorologico Nazionalemi chiedesse di estendere il mio programma e io avessi bisogno di memorizzare in citta ancheoggetti di classe diversa da localita? Perch cacciarsi nei guai e dover modificare la funzioneInserisciValori()? Lasciamo obj di tipo generico id, e limitiamoci a chiamare il suometodonuoviValori. Se obj di classe localita, il metodo nuoviValori chieder unatemperatura e unumidit e calcoler i valori medi. Se obj fosse di unaltra classe, sarsufficiente che questa implementi un metodo chiamato nuoviValori e il programma continuera funzionare! Ogni classe responsabile di gestire i propri dati, ogni oggetto responsabile dis stesso; main.m non ha bisogno di saperlo. Vedremo nel prossimo capitolo una primaapplicazione di questa importante novit.

    Quando lutente sceglie invece di visualizzare le temperature e le umidit medie per levarie localit inserite, la funzione MostraRisultati() viene chiamata. Ormai siete degliesperti: il meccanismo lo stesso della InserisciValori(). Un oggetto di classeNSEnumerator, accoppiato ad un ciclo while, permette di chiamare il metodo mostraValoridi ogni localit memorizzata nelloggetto citta. main.m non ha bisogno di sapere quali valorisiano memorizzati in ogni oggetto n come vadano trattati; loggetto a saperlo, lui che pensaad accettare i nuovi valori in ingresso e a mostrare i risultati finali in uscita.

    Infine, la funzione Esci() imposta a YES la variabile gDone, causando luscita dal loopdo-while della funzione main() alliterazione successiva. Gli oggetti citta e ap, che eranostati creati chiamando il metodo alloc e un metodo di tipo init, vengono ora liberati colmetodo release (ap va rilasciato per ultimo). Il programma pronto per uscire.

  • 7/30/2019 Tutorial Objective-C.pdf

    25/46

    25

    4. Sottoclassi

    Congratulazioni! Il Suo programma piaciuto tantissimo ai nostri Responsabili diProgetto. Attualmente lo stiamo utilizzando a pieno regime per monitorare temperatura e umiditmedie di oltre 20 localit sparse per il Paese. Gradiremmo che ora Lei lo espandesse cos daincludere una nuova categoria, citt con aeroporto, che oltre alle grandezze gi monitorate perle citt normali calcoli anche la visibilit media presso laeroporto. Certi della Sua proficuacollaborazione, porgiamo cordiali saluti. Questa la lettera che vi appena arrivata dalServizio Meteorologico Nazionale. Siete molto contenti, ma anche molto nervosi: vi sietecacciati in un bel guaio! Questaggiunta delle citt con aeroporto, che sono come le cittnormali ma hanno un pezzo in pi, vi costringer a riscrivere buona parte del codice, e il tempodisponibile poco. Accidenti! Ma un momento avete usato Objective-C! Ah, ma allora diverso! un giochetto da ragazzi: basta creare una sottoclasse di localita e modificareleggermente main.m.

    Rinfrancati, aprite il vostro editor di testo preferito e digitate quanto segue:

    #import

    #import "localita.h"

    @interface aeroporto : localita{

    double visibilitaMedia;}

    - (void)nuovaVisibilita:(double)unaVisibilita;

    @end

    Salvatelo come aeroporto.h (magari in una nuova cartella denominata Esempio2). Quindi,dopo aver copiato in questa cartella anche i file localita.h e localita.m dellEsempio1, tornate alvostro editor di testi preferito e digitate quanto segue:

    #import "aeroporto.h"

    @implementation aeroporto

    - (id)initWithName:(NSString *)unNome{

    [super initWithName:unNome];visibilitaMedia=0.0;

    return self;}

    - (void)nuoviValori{

    double v;

    [super nuoviValori];printf("Inserisci la visibilita' di oggi a mezzigiorno (m): ");scanf("%lg",&v);[self nuovaVisibilita:v];

    }

  • 7/30/2019 Tutorial Objective-C.pdf

    26/46

    26

    - (void)mostraValori{

    [super mostraValori];printf("Presso l'aeroporto la visibilita' media e' stata di %g

    m\n",visibilitaMedia);}

    - (void)nuovaVisibilita:(double)unaVisibilita{

    visibilitaMedia=(visibilitaMedia*(numeroGiorni-1)+unaVisibilita)/numeroGiorni;}

    @end

    Salvate il file come aeroporto.m, quindi copiate nella cartella Esempio2 il file main.m

    dellEsempio1 e, sempre usando il vostro editor di testo preferito, modificatelo come segue (lemodifiche rispetto alla versione usata per lEsempio1 sono riportate in grassetto):

    #include #import "localita.h"#import "aeroporto.h"#import

    #define kNuovaCitta 1#define kNuovoAeroporto 2#define kInserisciValori 3#define kMostraRisultati 4#define kEsci 0

    BOOL gDone=NO;NSMutableArray *citta;NSAutoreleasePool *ap;

    void MenuPrincipale(void);void NuovaCitta(void);void NuovoAeroporto(void);void InserisciValori(void);void MostraRisultati(void);

    void Esci(void);

    int main(void){

    ap=[[NSAutoreleasePool alloc] init];citta=[[NSMutableArray alloc] initWithCapacity:1];do{

    MenuPrincipale();} while(gDone==NO);return 0;

    }

  • 7/30/2019 Tutorial Objective-C.pdf

    27/46

    27

    void MenuPrincipale(void){

    int scelta;

    printf("Menu principale:\n\n");

    printf("1. Nuova citta'\n");printf("2. Nuovo aeroporto\n");printf("3. Inserisci nuovi valori\n");printf("4. Mostra i risultati\n");printf("\n");printf("0. Esci\n");printf("\n");printf("Inserisci la tua scelta: ");scanf("%d",&scelta);

    switch(scelta){

    case kNuovaCitta:NuovaCitta();break;case kNuovoAeroporto:NuovoAeroporto();break;

    case kInserisciValori:InserisciValori();break;

    case kMostraRisultati:MostraRisultati();

    break;case kEsci:Esci();break;

    default:printf("Inserisci un numero compreso tra 0 e 4!\n\n");break;

    }}

    void NuovaCitta(void){

    NSString *nome;char *nomeC;

    nomeC=calloc(40,sizeof(char));printf("\n");printf("Inserisci un nome per la nuova citta': ");scanf("%s",nomeC);nome=[[NSString alloc] initWithCString:nomeC];[citta addObject:[[localita alloc] initWithName:nome]];[nome release];free(nomeC);

    }

  • 7/30/2019 Tutorial Objective-C.pdf

    28/46

    28

    void NuovoAeroporto(void){ NSString *nome;char *nomeC;

    nomeC=calloc(40,sizeof(char));printf("\n");printf("Inserisci un nome per il nuovo aeroporto: ");scanf("%s",nomeC);nome=[[NSString alloc] initWithCString:nomeC];[citta addObject:[[aeroporto alloc] initWithName:nome]];[nome release];free(nomeC);}void InserisciValori(void){

    NSEnumerator *en;id obj;

    en=[citta objectEnumerator];while(obj=[en nextObject])

    [obj nuoviValori];}

    void MostraRisultati(void){

    NSEnumerator *en;

    id obj;

    en=[citta objectEnumerator];while(obj=[en nextObject])

    [obj mostraValori];}

    void Esci(void){

    gDone=YES;[citta release];[ap release];

    }

    Salvate il file come main.m, quindi compilate localita.m, aeroporto.m e main.m e infinelinkate i tre file oggetto (tutti e tre!) nel programma Esempio2:

    gcc -c localita.mgcc -c aeroporto.mgcc -c main.mgcc -o Esempio2 localita.o aeroporto.o main.o -lobjc -framework Cocoa

    Questa lultima volta che scriviamo come si fa, chiaro? Ora eseguite lEsempio2 egiocateci un po. Quando avete sperimentato quello che succede creando un po di citt e un podi aeroporti (che sta per citt con aeroporto), tornate qui e ne discutiamo.

  • 7/30/2019 Tutorial Objective-C.pdf

    29/46

    29

    Cominciamo dal file aeroporto.h. Come suggerisce la dichiarazione dellinterfaccia dellaclasse, aeroporto una sottoclasse di localita; ovvero, localita la classe genitrice o lasuperclasse di aeroporto. Perch questo giochetto funzioni, necessario importare il file diinterfaccia della classe genitrice, mediante listruzione #import localita.h. Nel filelocalita.h laver importato Cocoa.h aveva automaticamente reso disponibile il file di interfacciadella classe NSObject.

    Dire che aeroporto una sottoclasse di localita vuol dire che ogni aeroporto unalocalita, ma non viceversa. Ovvero: un aeroporto una localita con un nome e unsistema per monitorare temperatura e umidit medie; in aggiunta, un aeroporto pu anchemonitorare la visibilit media. Ecco un primo grande vantaggio di una sottoclasse: una citt conaeroporto , in fin dei conti, una citt; quindi, perch duplicare il codice gi esistente per lagestione delle citt? Allora creiamo una sottoclasse di localita e aggiungiamo quello cheserve per gestire le caratteristiche aggiuntive e peculiari di un aeroporto. Avremmo potutomodificare direttamente la classe localita, ma non sarebbe stato molto elegante: avremmodovuto modificare il metodo di inizializzazione, prevedendo un modo per specificare se sitrattasse di una citt o di una citt con aeroporto, quindi in una variabile della classe avremmodovuto memorizzare questa informazione, e nei metodi nuoviValori e mostraValori unblocco if avrebbe selezionato se richiedere o visualizzare oppure no le informazioni riguardantila visibilit a seconda che loggetto fosse identificato come citt con aeroporto o citt. Scomodoe molto poco elegante. Molto meglio una sottoclasse; come vedete, non stato necessariomodificare n localita.h n localita.m.

    Limplementazione della classe aeroporto avviene nel file aeroporto.m; quisovrascriviamo il metodo di inizializzazione perch vogliamo che la variabilevisibilitaMedia sia inizializzata a zero; tuttavia, poich un aeroporto una localita, inutile riscrivere il codice gi scritto: chiamiamo [super initWithName:], dove super indicaproprio la classe localita. Naturalmente, anzich chiamare il metodo [super init] come

    facevamo in localita.m, qui chiamiamo [super initWithName:] , perch initWithName: ilmetodo di inizializzazione della classe localita. Come sempre, il metodo di inizializzazionedeve restituire self.

    Non necessario sovrascrivere i metodi che non necessitano di essere modificati: ilmetododealloc, ad esempio, immutato, perch aeroporto non necessita di nessun nuovooggetto. Quando alla classe aeroporto verr richiesto dal sistema operativo di eseguire il suometodo dealloc, essa, non avendolo, dirotter automaticamente la richiesta alla classe genitrice,ovvero a localita, che eseguir felicemente il suo metodo dealloc.

    invece necessario sovrascrivere il metodo nuoviValori, perch un aeroporto qualcosa di diverso da una citt normale. Siccome le propriet di temperatura media e umidit

    media si gestiscono alla stessa maniera, chiamiamo [super nuoviValori] , che richiedeallutente le solite informazioni su temperatura e umidit a mezzogiorno, aggiornando poi ivalori medi. Poi, chiediamo allutente di inserire anche la visibilit presso laeroporto,calcolando la visibilit media chiamando il metodo nuovaVisibilita: della classe aeroporto.Questa una regola quasi generale: quando create una sottoclasse e ne sovrascrivete un metodo,facilmente questo chiamer il metodo omonimo della classe genitrice, e poi lo espander connuove linee di codice specifiche per le operazioni in pi che la sottoclasse deve compiere.

    Un discorso analogo vale per il metodo mostraValori : esso chiama [supermostraValori] per la parte in comune con le citt senza aeroporto; quindi visualizza il valoredella variabile visibilitaMedia.

    Una cosa molto pi interessante succede invece nel metodo nuovaVisibilita:: qui la

    visiblit media viene calcolata come si fa con lumidit nel metodo nuovaUmidita: nel file

  • 7/30/2019 Tutorial Objective-C.pdf

    30/46

    30

    localita