Sviluppo Di Applicazioni Su I Os

1,229 views

Published on

A brief introduction to Obecjtive-C and iOS programming

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,229
On SlideShare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
20
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Sviluppo Di Applicazioni Su I Os

  1. 1. SVILUPPO APP CON IOS Simone Kalb
  2. 2. E tu chi sei?Mi chiamo Simone KalbSocio del GulChJunior Software Developer at CRS4 (Open Media Center Lab)System AdministratorGNU/Linux e MacOS X avid user
  3. 3. Chi è iOS?(in numeri)3° piattaforma mobile venduta al Mondo [IDC]25% Market share del mercato degli smartphones [IDC]+0.7% aumento della quota di mercato in un anno [IDC]16.2M di unità vendute in un anno [IDC]90M di iPhone venduti da Giugno 2007 [9to5Mac]
  4. 4. ...e questo solo per iPhone!iOS per iPod Touch e iPadiPod Touch rappresenta il 38% di tutti i dispositivi con iOS [Ai]7.3M di iPad venduti dal lancio [WaBR]
  5. 5. Chi è iOS? (a parole)Un sistema : operativo moderno per piattaforme mobili e tablet ideale per gli sviluppatori fortemente orientato all’interazione gestuale
  6. 6. Chi lo usa?Tutte le maggiori aziende del settore IT hanno un’appLa maggior parte delle aziende di altri settoriMercato consumer gigantescoComunità di sviluppo fortissima a tutti i livelli
  7. 7. Perché sviluppare per iOS?Mercato in espansioneAPI semplici e ottimamente documentateOpportunità di businessCosti d’ingresso ridicoli
  8. 8. RequisitiPer sviluppare per iOS è necessario:Tempo (o una persona che te ne dedichi)(Gratis o quasi...)Un Mac (da €699,00)Un device con iOS (da €239,00)iOS Developer Program (da €79,00)Totale: da € 917,00
  9. 9. Ambiente di sviluppoLeopard (10.5)/Snow Leopard (10.6)/Lion (10.7) BetaXcode v.3.2.5/Xcode v.4.0.2/Xcode 4.2 (Beta)Interface Builder (compreso con Xcode)Eventualmente documentazione reperibile su http://bit.ly/mqPaHf
  10. 10. Cosa c’è dentro iOS?Core OS e Core ServicesLow-level routines, CFNetwork, CFoundation, SQLite, POSIXMedia Layer2D, 3D drawing, audio, video, OpenGL ES, Quartz, Core{Audio, Animation}Cocoa TouchHigh level collections, UIKit, GameKit, iAd, MapKit
  11. 11. Cosa vediamo quest’oggi?L’ambiente di sviluppoUna semplice applicazione Objective-C Classi IBOutlet e IBAction File’s Owner
  12. 12. Cosa vediamo in questo corso?Connessione di oggettiStruttura delle applicazioniI files XIBLa delegationLa gestione della MemoriaProperties
  13. 13. Cosa vediamo in questo corsoI view ControllerIl pattern MVCEsempietto finale
  14. 14. XcodeScarichiamo Xcode da qui (necessaria registrazione gratuita)Doppio click sull’icona .pkgInstalliamo anche i tools aggiuntiviScarichiamo la documentazione per iOS
  15. 15. INTERFACCIA DI XCODE Diamo uno sguardo a come si presenta
  16. 16. Iniziamo con la prima AppFile ->New Project (Cmd+Maiusc+N)View-based-ApplicationNominatelo HelloBuild-and-GoDone!
  17. 17. Primi passi con IBAprite HelloUserViewController.xibTrascinate una UILabel nella ViewLabel Size (cmd+3) portando l’altezza a 80.Carattere (Cmd+t) a 48Doppio click e scriviamo “Ciao iPhone”Salvare e Build-and-Go
  18. 18. RisultatoEcco come appare la nostra app
  19. 19. RotazioneHardware -> Rotate Left; Il testo non ruoterà.IB Finestra d’ispezione (Mela+3).Strut e spring, attiviamo le springs in tutte e quattro le direzioni.Specificare anche l’allineamento del testo in maniera che risulticentrato
  20. 20. Modifica al codiceHelloViewController.m - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation { // Return YES for supported orientations return YES }Ora premete Build-and-runComplimenti avete creato la vostra prima App per iPhone!
  21. 21. Due dritte per i TestSe siete registrati su ADC come iOS Developer potete: Creare dei certificati per i dispositivi Creare dei certificati per gli sviluppatori Diversi certificati per sviluppatori differenti Testare le applicazioni su device reali
  22. 22. Nell’editorClick sulla barra dell’anteprima del filePotete saltare alla funzione che più vi interessaRichiamare la documentazionePassare dal simulatore al device
  23. 23. OBJECTIVE-C
  24. 24. Objective chi?Sovrainsieme del C di K&RAd esso completamente compatibile ed aderenteÈ un C completamente ad oggetti ma != dal C++Agli oggetti sono inviati dei messaggiIl motore di runtime è incaricato dell’invio o meno dei messaggi
  25. 25. Aspetti della sintassiParentesi quadre nelle chiamatemioCarattere = [miaStringa characterAtIndex :3];I parametri hanno sempre un nome e due punti (keyword)Le classi hanno un file header e uno d’implementazionePersona.m/Persona.hAltro codice utile può essere importato con import nel file header
  26. 26. Tipi di classiFoundation contiene una serie sterminata di classi di largo uso: NSString (al posto di char*) NS{Array, Set, Dictionary}/NSMutable{Array, Set, Dictionary} NSNumber per numeri di tutti i tipi UIButton, UITextView,UITableView da UIKit NSURL che contiene wrapper per file:// e http:// e alt.
  27. 27. Organizzazione files in XcodeClassesOther SourcesResourcesFrameworksProducts
  28. 28. Modifichiamo la nostra AppChiudiamo il nostro progetto precedente e creiamone un altroAnche questa volta scegliamo View-Based ApplicationChiamiamolo HelloUserLa nostra App saluterà l’utente col il suo nome precedentementescritto
  29. 29. Cosa dobbiamo fare?Dobbiamo creare una label per visualizzare il messaggioUn campo di testo per il nome dell’utenteUn pulsante per accettare l’inputUn metodo che aggiorni l’etichetta di testo una volta premuto ilpulsante
  30. 30. ConnessioniPer fare ciò che abbiamo descritto è necessario Istruire l’interfaccia grafica con gli elementi Istruire il codice con i metodi appropriati Un riferimento dal codice alla UI si chiama outlet Per definire un oggetto del genere si ricorre ad IBOutlet Per ogni azione, invece, si usa IBAction
  31. 31. IBOutlet e IBAction IBOutlet è una finta “presa” che fa da ponte tra il codice e l’UI IBAction gestisce le azioni generate dall’interfaccia Verranno entrambe dichiarate nel file .h e istanziate nel .m@interface HelloUserViewController : UIViewController <UITextFieldDelegate> {!! IBOutlet UILabel *helloLabel;! IBOutlet UITextField *nameField;!}-(IBAction)sayHello: (id)sender;
  32. 32. ParticolaritàIBAction equivale a voidIBAction per il pulsante, non c’è bisogno dell’outletPattern (id) senderOggetto id
  33. 33. IB Disegnare l’interfacciaAprire HelloUserViewController.xibVedremo due proxy e una viewInseriamo una UILabel, una UITextField e un UIButtonPer semplicità la UILabel mettiamola vuotaNella finestra d’ispezione utilizzare un Placeholder nella UITextNel bottone utilizziamo la dicitura “Dimmi Ciao”
  34. 34. Uno sguardo al File’s OwnerÈ un proxy alla viewCliccando col tasto destro si vedono sia gli outlet che le azioniOgnuno di questi deve essere connesso ad un elemento di UIIn questo caso vedremo helloLabel e nameFieldVedremo la sua importanza nel prosieguo
  35. 35. Connettere gli oggettiConnettiamo gli oggetti dell’UI con File’s OwnerOgni oggetto della UI viene connesso con un IBOutletIl bottone invece verrà connesso ad un IBActionUnire l’evento TouchUp Inside con IBAction del File’s OwnerIn questo caso l’unica scelta disponibile è sayHello:
  36. 36. Implementazione dell’azione Adesso implementiamo nel file HelloUserViewController.m-(void) sayHello:(id)sender {!! NSString *username = nameField.text;! NSString *helloMessage = [[NSString alloc] initWithFormat:@"Hello %@", username];! helloLabel.text = helloMessage;! [helloMessage release];! nameField.text = NULL;! [nameField resignFirstResponder];}
  37. 37. Commentiamo il codiceOtteniamo la stringa inserita dall’utente (proprietà)Instanza di una stringa a cui aggiungiamo “Hello”%@ è uno specificatore di formato per una stringa di ∀ formatoImpostiamo la proprietà .text per la label con la stringa creataCi disfacciamo della stringa non essendo più usataReinizializziamo il campo testo della UITextField
  38. 38. Due parole sulle ApplicazioniDiamo uno sguardo a main.mUIApplicationMain è la funzione principale che si occupa delciclo di vita dell’applicazioneI due ultimi argomenti sono la classe principale e la sua delegataLa delegata gestisce gli eventi nel ciclo di vita dell’applicazioneIn questo caso si carica tutto dai file NIB
  39. 39. Andiamo a vedere i NIBApriamo i file MainWindows.xib (come ci suggerisce Info.plist)Icona di Delegato (HelloUserApp Delegate)ViewController (HelloUserViewController)Finestra (UIWindow)File’s OwnerFirst Responder
  40. 40. Application, Life CycleIl delegato è connesso all’unica finestra dell’applicazioneIl ViewController carica la sua view da un NIBIl primo metodo è applicationDidFinishLaunching: che carica con callback come initWithCoder: e viewDidLoad: il primo se caricato il NIB l’altro se caricata la view
  41. 41. La delegationLa delegation è uno dei pattern più importanti dello sviluppoUn oggetto chiama il suo unico delegato quando si verificanodeterminati eventiDal punto di vista del delegato è una callback “Chiamami quando succede questo”Da quello del delegante è uno scarico di responsabilità “Non so che fare, pensaci tu”
  42. 42. Come funziona un delegato?Objective-C usa un protocollo di delega formale definito inUIApplicationDelegateQuesto sarebbe come una normale classeDescrive, però quando i metodi saranno richiamatiCosa fare quando l’implementatore deve fare quando li richiamaDeve dichiarare che la propria classe implementa il protocollo
  43. 43. Perché tutta questa storia?Nella nostra app la tastiera non è scomparsa dopo il tapIl tasto d’invio non ha avuto effettiBisogna definire un delegato per il campo di testoPer la tastiera è necessario lasciare il ruolo di primo risponditorein sayHello: aggiungiamo la riga:[nameField resignFirstResponder];
  44. 44. ..e per il tasto d’invio?Esaminiamo la classe delegata UITextFieldDelegateDovremo prendere in considerazione textFieldShouldReturn:Quindi dovremo semplicemente overloadare il metodoE dire alla classe che implementa UITextFieldDelegate
  45. 45. Modifichiamo il codice HelloUserViewController.h@interface HelloUserViewController : UIViewController <UITextFieldDelegate> {...} HelloUserViewController.m-(BOOL)textFieldShouldReturn:(UITextField *)textField{!! [textField resignFirstResponder];! return YES;!} Ora basta un Build and Run.
  46. 46. Gestione della memoriaLa gestione della memoria su device mobili è fondamentaleÈ necessario appena possibile liberare quanta più memoriaOgni applicazione per iPhone deve gestirla correttamenteTutti gli oggetti sono allocati con conteggio riferimenti pari a 1retain incrementa il conteggio, release lo decrementaQuando il valore arriva a zero l’oggetto è pronto per il rilascio
  47. 47. Regole d’ingaggioLe regole auree sono: se siete proprietari d’un oggetto lo dovete rilasciare se fate new alloc copy, dovrete fare un release non rilasciate oggetti non vostri se volete appropriarvene fate un retain autorelease serve per metodi che restituiscono oggetti non in grado di gestire
  48. 48. EsempioLa NSString ha un metodo stringWithFormat:Questo è un metodo a rilascio automaticoNon ha alcuna differenza con alloc e initWithFormat:Questa chiamata non richiede il rilascio dell’oggetto
  49. 49. Rilasciamo gli oggetti In HelloUserViewController:- (void)dealloc {! [textField release];! [nameField release]; [super dealloc];} Così siamo sicuri di rilasciare correttamente tutte le variabili in gioco
  50. 50. Le propertiesVariabili istanza con nomi che seguono convenzioni in r/wAccesso a slot per i getter e setter tramite l’operatore punto[myLabel setText: myString]; - primamyLabel.text = myString; - dopoLe properties vanno definite nel file header fuori dalle parentesi
  51. 51. Come si definisce la property@property (attributi) propType propName;Vantaggio in termini di leggibilità del codice ma anche: Gestione della memoria (assign, retain, copy) Gestione threads (thread-safe, ma nonatomic è più rapido) @synthesize genera automaticamente i metodi get/set
  52. 52. Modifica del codice HelloUserViewController.h@property(nonatomic, retain) UILabel *helloLabel;@property(nonatomic, retain) UITextField *nameField; HelloUserViewController.m@synthesize helloLabel, nameField; Potremo richiamare helloUserViewController.nameField.text ovunque nel codice anche da altre classi
  53. 53. RicapitoliamoLe variabili istanza nel .h se le inseriamo nel .m gli altri non vi possono accedereGli oggetti sono creati nel codice o in IBGestite le connessioni con IBOutlets e IBAction in IB<nomeProtocollo> se volete implementare un delegato on in IBDichiarare le properties, sintetizzarle e gestire la memoria
  54. 54. I VIEW CONTROLLER
  55. 55. MVCAltro paradigma fondamentale in CocoaIl View Controller è la “C”Interagisce con la View (“V”)Per rappresentare un Modello (“M”) astrattoÈ un modello sul quale si basa gran parte di quel che vedremo
  56. 56. Apriamo un nuovo progettoFile->New Project ->View-Based ApplicationChiamiamolo MovieApriamo MovieViewController.xibAggiungiamo un UIButton alla ViewAndiamo a modificare il codice
  57. 57. Aggiungiamo una Action In MovieViewController.h:-(IBAction)done; Torniamo in IB e facciamo un collegamento tra File’s Owner e il metodo TouchUp Inside Ora nel file d’implementazione:-(IBAction)done {! NSLog(@"Chiamato il metodo edit");} Ora provate a compilare..
  58. 58. Creiamo il modello Add->New File; Objective-C Class; Chiamiamola Movie come subclass di NSObject e crea anche il .h@interface Movie : NSObject {! NSString *title;! NSNumber *boxOfficeGross;! NSString *summary;!}-(id)initWithTitle:(NSString *)newTitle! boxOfficeGross:(NSNumber *)newBoxOfficeGross! ! summary:(NSString *)newSummary;@property(nonatomic, copy)NSString *title;@property(nonatomic, copy)NSNumber *boxOfficeGross;@property(nonatomic, copy) NSString *summary;
  59. 59. ..e nell’implementazione @implementation Movie@synthesize title;@synthesize boxOfficeGross;@synthesize summary;-(id)initWithTitle:(NSString *)newTitle! boxOfficeGross:(NSNumber *)newBoxOfficeGross! ! summary:(NSString *)newSummary {!! self = [super init];! if(nil != self) {! ! self.title = newTitle;! ! self.boxOfficeGross = newBoxOfficeGross;! ! self.summary = newSummary;! }! return self;!}-(void) dealloc {!! self.title = nil;! self.boxOfficeGross = nil;! self.summary = nil;! [super dealloc];!}@end
  60. 60. Due precisazioni dueself=[super init] serve per gestire i casi in cui la superclasserestituisca oggetti diversi (alcuni FWs lo fanno)Serve solo in casi di modelli personalizzatiAbbiamo impostato le properties come copy quindi un dealloc ènecessarioÈ buona norma mettere a nil le variabili istanzaLa classe ha solo un metodo initWithTitle:
  61. 61. Aggiunta di outlet e azioni Nel file MovieViewController.h#import <UIKit/UIKit.h>@class Movie;@interface MovieViewController : UIViewController {!! Movie *movie;! UILabel *titleLabel;! UILabel *boxOfficeGrossLabel;! UILabel *summaryLabel;! MovieEditorViewController *editingViewController;}@property(nonatomic, retain) Movie *movie;@property(nonatomic, retain) IBOutlet UILabel *titleLabel;@property(nonatomic, retain) IBOutlet UILabel *boxOfficeGrossLabel;@property(nonatomic, retain) IBOutlet UILabel *summaryLabel;@property(nonatomic, retain) IBOutlet MovieEditorViewController *editingViewController;-(IBAction)edit;@end Da notare come la @class Movie; sia una dichiarazione forward
  62. 62. E nel file .m Adesso importiamo la classe Movie e sintetizziamo le properties:#import "MovieViewController.h"#import "Movie.h"@implementation MovieViewController@synthesize titleLabel;@synthesize boxOfficeGrossLabel;@synthesize summaryLabel;@synthesize movie; Ora siamo pronti alla modifica dell’interfaccia in IB Apriamo MovieViewController.xib
  63. 63. Le modifiche all’interfacciaNella nostra view inseriamo ben tre UILabel leggermentedistanziateConnettete correttamente gli outlets con le labelsSalvate come al solito IB (altrimenti non succede nulla)Ritorniamo al codiceImplementiamo il controller in modo che visualizzi di dati
  64. 64. Inizializzazione della View Non appena si carica la view vogliamo che appaia qualcosa:- (void)viewDidLoad { [super viewDidLoad];! Movie *newMovie = [[[Movie alloc]! ! ! ! ! ! initWithTitle:@"Iron Man"! ! ! ! ! ! boxOfficeGross:[NSNumber numberWithFloat:650000000.00]! ! ! ! ! ! summary:@"Un tipo sveglio costruisce armi fighe"] autorelease];! self.movie = newMovie;} Succede che quando un controller riceve una richiesta vede se ha già una view, in caso contrario carica loadView: Poi viene richiamato viewDidLoad: non appena visualizzata la view, infine viene chiamato viewWillAppear:
  65. 65. Non appena la view è caricata Ecco il codice per MovieViewController.m-(void)viewWillAppear:(BOOL)animated {! [super viewWillAppear:animated];! self.titleLabel.text = self.movie.title;! NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];! [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];! self.boxOfficeGrossLabel.text = [formatter stringFromNumber:self.movie.boxOfficeGross];! [formatter release];! self.summaryLabel.text = self.movie.summary;!} Il codice aggiunge i dati per gli oggetti Movie nei campi di testo NSNumberFormatter viene usato per convertire numeri e stringhe
  66. 66. Creazione di un nuovo VCScegliete Add->NewFile e scegliete UIViewController SubclassRicordatevi di NON spuntare with XIB for user interfaceMovieEditorViewController sarà il nome del nuovo VCAndiamo subito a modificare il codice del controllerPartiamo dall’header file
  67. 67. MovieEditorViewController.h @class Movie;@interface MovieEditorViewController : UIViewController <UITextFieldDelegate> {! UITextField *titleField;! UITextField *boxOfficeGrossField;! UITextField *summaryField;! Movie *movie;}@property(nonatomic, retain) IBOutlet UITextField *titleField;@property(nonatomic, retain) IBOutlet UITextField *boxOfficeGrossField;@property(nonatomic, retain) IBOutlet UITextField *summaryField;@property(nonatomic, retain) Movie *movie;-(IBAction)done;@end Notiamo che implementiamo l’interfaccia UITextFieldDelegate E un metodo d’interfaccia per quando avremo finito, done:
  68. 68. MovieEditorViewController.m Ricordiamoci di aggiungere #import Movie.h; e @synthesize per le proprietà -(void)viewWillAppear:(BOOL)animated {! [super viewWillAppear:animated];! self.titleField.text = self.movie.title;! self.summaryField.text = self.movie.summary;! NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];! [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];! self.boxOfficeGrossField.text = [formatter stringFromNumber:self.movie.boxOfficeGross];! [formatter release];!} Notiamo come convertiamo una stringa il numero boxOfficeGross mediante un NSNumerFormatter
  69. 69. MovieEditorViewController.m Dismettiamo l’attuale view modale per l’azione “Done”-(IBAction)done {! [[self parentViewController] dismissModalViewControllerAnimated:YES];!} Renderemo in seguito il view controller delegato per ciascun campo di testo in IB Come prima vedremo come implementare i metodi textFielShouldReturn: e textFieldDidEndEditing:
  70. 70. textFieldShouldReturn: -(BOOL)textFieldShouldReturn:(UITextField *)textField; {! [textField resignFirstResponder];! return YES;!} Approfittiamo del momento in cui il controllo rilascia il ruolo di primo risponditore per inserire i dati nell’oggetto:-(void) textFieldDidEndEditing:(UITextField *)textField {! if(textField == self.titleField) {! ! self.movie.title = self.titleField.text;! } else if(textField == self.boxOfficeGrossField) {! ! NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];! ! [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];! ! self.movie.boxOfficeGross = [formatter numberFromString:self.boxOfficeGrossField.text];! ! [formatter release];! }else if(textField == self.summaryField) {! ! self.movie.summary = self.summaryField.text;! }} È necessario distinguere i valori digitati nei vari campi
  71. 71. Creazione dell’UIAdd->New File da iOS->User Interfaces e scegliete View XIBChiamate il tutto MovieEditorViewController.xibCambiamo subito il File’s Owner conMovieEditorViewControllerAggiungete tre campi di testo ed un pulsanteConnettete ciascuno degli outlet dei campi di testoConnettete infine l’IBAction e i delegati
  72. 72. Tenere sempre presente la tastieraPensate sempre alla tastiera che deve apparire per i campi di testoPassword? Numeri? URL? Ad ognuno la sua tastieraScegliere l’indicazione corretta del tasto d’invioL’uso di un segnaposto è talvolta incoraggiatoLa funzionalità Clear When Editing Begin va usata con cautela
  73. 73. Creazione del MEVCAbbiamo quasi finito, ci manca solo come arrivare alla nuova viewI passi da seguire sono i seguenti: Aggiungere un outlet a MVC per l’istanza di MEVC Aggiornare il metodo edit: Creare un’istanza di MEVC nel NIB di MVC e effettuare la connessione
  74. 74. MVC.h Il file MVC.h risulterà ora così:#import <UIKit/UIKit.h>@class Movie;@class MovieEditorViewController;@interface MovieViewController : UIViewController {!! Movie *movie;! UILabel *titleLabel;! UILabel *boxOfficeGrossLabel;! UILabel *summaryLabel;! MovieEditorViewController *editingViewController;}@property(nonatomic, retain) Movie *movie;@property(nonatomic, retain) IBOutlet UILabel *titleLabel;@property(nonatomic, retain) IBOutlet UILabel *boxOfficeGrossLabel;@property(nonatomic, retain) IBOutlet UILabel *summaryLabel;@property(nonatomic, retain) IBOutlet MovieEditorViewController *editingViewController;-(IBAction)edit;@end
  75. 75. Aggiorniamo edit: Non dimentichiamo di aggiungere l’istruzione #import e i synthesize per ciascuna proprietà. Il metodo edit: diventerà il seguente-(IBAction)edit {! self.editingViewController.movie = self.movie;! [self presentModalViewController:self.editingViewController! ! ! ! ! ! ! animated:YES];} Ora aggiungete un view controller al NIB di MVC, impostate MEVC come classe di questo NIB Infine connettete all’outlet evc del File’s Owner al nuovo VC
  76. 76. Fine!Finalmente l’applicazione dovrebbe essere prontaFacciamo click su Build and RunVerifichiamo che l’app funzioni correttamenteIn caso contrario scorriamo tutte le proprietà di IB e del codiceIn molti casi l’errore s’annida in un riferimento inesistente in IBAltre volte mancano le dichiarazioni forward o properties nonsintetizzate
  77. 77. Prima di terminareAlcuni riferimenti Twitter @simonekalb LinkedIn Simone Kalb email: simone@nodelay.org
  78. 78. Prima di concludere (ancora..)Un ringraziamento speciale al Java User Group SardegnaONLUS e a Massimiliano DessìUn altro ringraziamento particolare a tutti voi che sieteintervenuti qui staseraLe slides sono disponibili su SlideSharePer qualsiasi altra cosa chiedete pure..
  79. 79. QUESTION TIME
  80. 80. THE END!

×