Semplificazione dell'utilizzo dell'API RESTful e della persistenza dei dati su iOS con Mantle e Realm
Pubblicato: 2022-03-11Ogni sviluppatore iOS ha familiarità con Core Data, un grafico a oggetti e un framework di persistenza di Apple. Oltre alla persistenza dei dati in locale, il framework viene fornito con una serie di funzionalità avanzate, come il rilevamento delle modifiche agli oggetti e l'annullamento. Queste funzionalità, sebbene utili in molti casi, non sono gratuite. Richiede molto codice standard e il framework nel suo insieme ha una curva di apprendimento ripida.
Nel 2014, Realm, un database mobile, è stato rilasciato e ha preso d'assalto il mondo dello sviluppo. Se tutto ciò di cui abbiamo bisogno è persistere i dati in locale, Realm è una buona alternativa. Dopotutto, non tutti i casi d'uso richiedono le funzionalità avanzate di Core Data. Realm è estremamente facile da usare e, a differenza di Core Data, richiede pochissimo codice standard. È anche thread-safe e si dice che sia più veloce del framework di persistenza di Apple.
Nella maggior parte delle moderne applicazioni mobili, la persistenza dei dati risolve metà del problema. Spesso abbiamo bisogno di recuperare i dati da un servizio remoto, di solito tramite un'API RESTful. È qui che entra in gioco Mantle. È un framework modello open source per Cocoa e Cocoa Touch. Mantle semplifica notevolmente la scrittura di modelli di dati per l'interazione con le API che utilizzano JSON come formato di scambio dati.
In questo articolo, creeremo un'applicazione iOS che recupera un elenco di articoli insieme ai collegamenti ad essi dall'API di ricerca articoli del New York Times v2. L'elenco verrà recuperato utilizzando una richiesta HTTP GET standard, con modelli di richiesta e risposta creati utilizzando Mantle. Vedremo quanto è facile con Mantle gestire le trasformazioni dei valori (ad es. da NSDate a string). Una volta recuperati i dati, li renderemo persistenti localmente utilizzando Realm. Tutto questo con un codice boilerplate minimo.
API RESTful - Per iniziare
Iniziamo creando un nuovo progetto Xcode “Master-Detail Application” per iOS denominato “RealmMantleTutorial”. Aggiungeremo framework ad esso usando CocoaPods. Il podfile dovrebbe essere simile al seguente:
pod 'Mantle' pod 'Realm' pod 'AFNetworking'
Una volta installati i pod, possiamo aprire l'area di lavoro MantleRealmTutorial appena creata. Come avrai notato, è stato installato anche il famoso framework AFNetworking. Lo useremo per eseguire richieste all'API.
Come accennato nell'introduzione, il New York Times fornisce un'eccellente API per la ricerca di articoli. Per utilizzarlo, è necessario registrarsi per ottenere una chiave di accesso all'API. Questo può essere fatto su http://developer.nytimes.com. Con la chiave API in mano siamo pronti per iniziare con la codifica.
Prima di approfondire la creazione di modelli di dati Mantle, è necessario rendere operativo il nostro livello di rete. Creiamo un nuovo gruppo in Xcode e chiamiamolo Network. In questo gruppo creeremo due classi. Chiamiamo il primo SessionManager e assicuriamoci che sia derivato da AFHTTPSessionManager che è una classe di gestione delle sessioni di AFNetworking , il delizioso framework di rete. La nostra classe SessionManager sarà un oggetto singleton che utilizzeremo per eseguire richieste di ricezione all'API. Una volta che la classe è stata creata, copia il codice seguente rispettivamente nei file di interfaccia e di implementazione.
#import "AFHTTPSessionManager.h" @interface SessionManager : AFHTTPSessionManager + (id)sharedManager; @end
#import "SessionManager.h" static NSString *const kBaseURL = @"http://api.nytimes.com"; @implementation SessionManager - (id)init { self = [super initWithBaseURL:[NSURL URLWithString:kBaseURL]]; if(!self) return nil; self.responseSerializer = [AFJSONResponseSerializer serializer]; self.requestSerializer = [AFJSONRequestSerializer serializer]; return self; } + (id)sharedManager { static SessionManager *_sessionManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sessionManager = [[self alloc] init]; }); return _sessionManager; } @end
Il gestore di sessione viene inizializzato con l'URL di base definito nella variabile statica kBaseURL . Utilizzerà anche serializzatori di richieste e risposte JSON.
Ora la seconda classe che creeremo nel gruppo Network si chiamerà APIManager . Deve essere derivato dalla nostra classe SessionManager appena creata. Una volta creati i modelli di dati necessari, aggiungeremo un metodo ad ApiManager che verrà utilizzato per richiedere un elenco di articoli dall'API.
Panoramica dell'API di ricerca articoli del New York Times
La documentazione ufficiale per questa eccellente API è disponibile all'indirizzo http://developer.nytimes.com/…/article_search_api_v2. Quello che faremo è utilizzare il seguente endpoint:
http://api.nytimes.com/svc/search/v2/articlesearch
... per recuperare gli articoli trovati utilizzando un termine di ricerca di nostra scelta delimitato da un intervallo di date. Ad esempio, ciò che potremmo fare è chiedere all'API di restituire un elenco di tutti gli articoli apparsi sul New York Times che hanno avuto a che fare con il basket nei primi sette giorni di luglio 2015. Secondo la documentazione dell'API, per farlo dobbiamo impostare i seguenti parametri nella richiesta get a quell'endpoint:
Parametro | Valore |
Q | "pallacanestro" |
data_inizio | “20150701” |
data di fine | “20150707” |
La risposta dell'API è piuttosto complessa. Di seguito è riportata la risposta per una richiesta con i parametri di cui sopra limitato a un solo articolo (un elemento nell'array docs) con numerosi campi omessi per chiarezza.
{ "response": { "docs": [ { "web_url": "http://www.nytimes.com/2015/07/04/sports/basketball/robin-lopez-and-knicks-are-close-to-a-deal.html", "lead_paragraph": "Lopez, a 7-foot center, joined Arron Afflalo, a 6-foot-5 guard, as the Knicks' key acquisitions in free agency. He is expected to solidify the Knicks' interior defense.", "abstract": null, "print_page": "1", "source": "The New York Times", "pub_date": "2015-07-04T00:00:00Z", "document_type": "article", "news_desk": "Sports", "section_name": "Sports", "subsection_name": "Pro Basketball", "type_of_material": "News", "_id": "5596e7ac38f0d84c0655cb28", "word_count": "879" } ] }, "status": "OK", "copyright": "Copyright (c) 2013 The New York Times Company. All Rights Reserved." }
Ciò che otteniamo fondamentalmente in risposta sono tre campi. Il primo chiamato response contiene l'array docs , che a sua volta contiene elementi che rappresentano articoli. Gli altri due campi sono stato e copyright . Ora che sappiamo come funziona l'API, è il momento di creare modelli di dati utilizzando Mantle.
Introduzione al mantello
Come accennato in precedenza, Mantle è un framework open source che semplifica notevolmente la scrittura di modelli di dati. Iniziamo creando un modello di richiesta elenco articoli. Chiamiamo questa classe ArticleListRequestModel e assicuriamoci che sia derivata da MTLModel , che è una classe da cui dovrebbero derivare tutti i modelli Mantle. Inoltre, rendiamolo conforme al protocollo MTLJSONSerializing . Il nostro modello di richiesta dovrebbe avere tre proprietà di tipo adatto: query, articleFromDate e articleToDate . Solo per assicurarmi che il nostro progetto sia ben organizzato, suggerisco di inserire questa classe nel gruppo Modelli .
Ecco come dovrebbe apparire il file di interfaccia di ArticleListRequestModel :
#import "MTLModel.h" #import "Mantle.h" @interface ArticleListRequestModel : MTLModel <MTLJSONSerializing> @property (nonatomic, copy) NSString *query; @property (nonatomic, copy) NSDate *articlesFromDate; @property (nonatomic, copy) NSDate *articlesToDate; @end
Ora, se cerchiamo i documenti per il nostro endpoint di ricerca articolo o diamo un'occhiata alla tabella con i parametri di richiesta sopra, noteremo che i nomi delle variabili nella richiesta API differiscono da quelli nel nostro modello di richiesta. Mantle gestisce questo in modo efficiente usando il metodo:
+ (NSDictionary *)JSONKeyPathsByPropertyKey.
Ecco come dovrebbe essere implementato questo metodo nell'implementazione del nostro modello di richiesta:
#import "ArticleListRequestModel.h" @implementation ArticleListRequestModel #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"query": @"q", @"articlesFromDate": @"begin_date", @"articlesToDate": @"end_date" }; } @end
L'implementazione di questo metodo specifica come le proprietà del modello vengono mappate nelle sue rappresentazioni JSON. Una volta implementato il metodo JSONKeyPathsByPropertyKey , possiamo ottenere una rappresentazione del dizionario JSON del modello con il metodo della classe +[MTLJSONAdapter JSONArrayForModels:]
.
Una cosa che rimane, come sappiamo dall'elenco dei parametri, è che entrambi i parametri della data devono essere nel formato "AAAAMMGG". È qui che Mantle diventa molto utile. Possiamo aggiungere una trasformazione del valore personalizzata per qualsiasi proprietà implementando il metodo facoltativo +<propertyName>JSONTransformer
. Implementandolo, diciamo a Mantle come deve essere trasformato il valore di un campo JSON specifico durante la deserializzazione JSON. Possiamo anche implementare un trasformatore reversibile che verrà utilizzato durante la creazione di un JSON dal modello. Poiché abbiamo bisogno di trasformare un oggetto NSDate in una stringa, utilizzeremo anche la classe NSDataFormatter . Ecco l'implementazione completa della classe ArticleListRequestModel :
#import "ArticleListRequestModel.h" @implementation ArticleListRequestModel + (NSDateFormatter *)dateFormatter { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateFormat = @"yyyyMMdd"; return dateFormatter; } #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"query": @"q", @"articlesFromDate": @"begin_date", @"articlesToDate": @"end_date" }; } #pragma mark - JSON Transformers + (NSValueTransformer *)articlesToDateJSONTransformer { return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter dateFromString:dateString]; } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter stringFromDate:date]; }]; } + (NSValueTransformer *)articlesFromDateJSONTransformer { return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter dateFromString:dateString]; } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) { return [self.dateFormatter stringFromDate:date]; }]; } @end
Un'altra grande caratteristica di Mantle è che tutti questi modelli sono conformi al protocollo NSCoding , oltre a implementare metodi isEqual e hash .
Come abbiamo già visto, il JSON risultante dalla chiamata API contiene un array di oggetti che rappresentano articoli. Se vogliamo modellare questa risposta usando Mantle, dovremo creare due modelli di dati separati. Uno modellerebbe oggetti che rappresentano articoli (elementi dell'array docs ) e l'altro modellerebbe l'intera risposta JSON ad eccezione degli elementi dell'array docs. Ora, non dobbiamo mappare ogni singola proprietà dal JSON in ingresso nei nostri modelli di dati. Supponiamo di essere interessati solo a due campi di oggetti articolo, e quelli sarebbero lead_paragraph e web_url . La classe ArticleModel è piuttosto semplice da implementare, come possiamo vedere di seguito.
#import "MTLModel.h" #import <Mantle/Mantle.h> @interface ArticleModel : MTLModel <MTLJSONSerializing> @property (nonatomic, copy) NSString *leadParagraph; @property (nonatomic, copy) NSString *url; @end
#import "ArticleModel.h" @implementation ArticleModel #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"leadParagraph": @"lead_paragraph", @"url": @"web_url" }; } @end
Ora che il modello dell'articolo è stato definito, possiamo completare la definizione del modello di risposta creando un modello per l'elenco degli articoli. Ecco come apparirà il modello di risposta ArticleList della classe.

#import "MTLModel.h" #import <Mantle/Mantle.h> #import "ArticleModel.h" @interface ArticleListResponseModel : MTLModel <MTLJSONSerializing> @property (nonatomic, copy) NSArray *articles; @property (nonatomic, copy) NSString *status; @end
#import "ArticleListResponseModel.h" @class ArticleModel; @implementation ArticleListResponseModel #pragma mark - Mantle JSONKeyPathsByPropertyKey + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"articles" : @"response.docs", @"status" : @"status" }; } #pragma mark - JSON Transformer + (NSValueTransformer *)articlesJSONTransformer { return [MTLJSONAdapter arrayTransformerWithModelClass:ArticleModel.class]; } @end
Questa classe ha solo due proprietà: status e articoli . Se lo confrontiamo con la risposta dall'endpoint, vedremo che il terzo copyright dell'attributo JSON non verrà mappato nel modello di risposta. Se osserviamo il metodo itemsJSONTransformer , vedremo che restituisce un trasformatore di valore per un array contenente oggetti della classe ArticleModel .
Vale anche la pena notare che nel metodo JSONKeyPathsByPropertyKey , gli articoli della proprietà del modello corrispondono ai documenti dell'array nidificati all'interno della risposta dell'attributo JSON.
A questo punto dovremmo avere tre classi di modelli implementate: ArticleListRequestModel, ArticleModel e ArticleListResponseModel.
Prima richiesta API
Ora che abbiamo implementato tutti i modelli di dati, è tempo di tornare alla classe APIManager per implementare il metodo che utilizzeremo per eseguire le richieste GET all'API. Il metodo:
- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure
prende un modello di richiesta ArticleListRequestModel come parametro e restituisce un ArticleListResponseModel in caso di successo o un NSError in caso contrario. L'implementazione di questo metodo utilizza AFNetworking per eseguire una richiesta GET all'API. Si prega di notare che per fare una richiesta API di successo dobbiamo fornire una chiave che può essere ottenuta come menzionato in precedenza, registrandosi su http://developer.nytimes.com.
#import "SessionManager.h" #import "ArticleListRequestModel.h" #import "ArticleListResponseModel.h" @interface APIManager : SessionManager - (NSURLSessionDataTask *)getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure; @end
#import "APIManager.h" #import "Mantle.h" static NSString *const kArticlesListPath = @"/svc/search/v2/articlesearch.json"; static NSString *const kApiKey = @"replace this with your own key"; @implementation APIManager - (NSURLSessionDataTask *)getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure{ NSDictionary *parameters = [MTLJSONAdapter JSONDictionaryFromModel:requestModel error:nil]; NSMutableDictionary *parametersWithKey = [[NSMutableDictionary alloc] initWithDictionary:parameters]; [parametersWithKey setObject:kApiKey forKey:@"api-key"]; return [self GET:kArticlesListPath parameters:parametersWithKey success:^(NSURLSessionDataTask *task, id responseObject) { NSDictionary *responseDictionary = (NSDictionary *)responseObject; NSError *error; ArticleListResponseModel *list = [MTLJSONAdapter modelOfClass:ArticleListResponseModel.class fromJSONDictionary:responseDictionary error:&error]; success(list); } failure:^(NSURLSessionDataTask *task, NSError *error) { failure(error); }]; }
Ci sono due cose molto importanti che accadono nell'implementazione di questo metodo. Per prima cosa diamo un'occhiata a questa riga:
NSDictionary *parameters = [MTLJSONAdapter JSONDictionaryFromModel:requestModel error:nil];
Quello che sta succedendo qui è che usando il metodo fornito dalla classe MTLJSONAdapter otteniamo una rappresentazione NSDictionary del nostro modello di dati. Tale rappresentazione rispecchia il JSON che verrà inviato all'API. È qui che sta la bellezza di Mantle. Avendo implementato i metodi JSONKeyPathsByPropertyKey e +<propertyName>JSONTransformer
nella classe ArticleListRequestModel, possiamo ottenere la rappresentazione JSON corretta del nostro modello di dati in pochissimo tempo con una sola riga di codice.
Il mantello ci consente anche di eseguire trasformazioni anche nell'altra direzione. Ed è esattamente ciò che sta accadendo con i dati ricevuti dall'API. Il NSDictionary che riceviamo viene mappato in un oggetto della classe ArticleListResponseModel utilizzando il seguente metodo di classe:
ArticleListResponseModel *list = [MTLJSONAdapter modelOfClass:ArticleListResponseModel.class fromJSONDictionary:responseDictionary error:&error];
Dati persistenti con Realm
Ora che siamo in grado di recuperare i dati da un'API remota, è il momento di mantenerli. Come accennato nell'introduzione, lo faremo usando Realm. Realm è un database mobile e un sostituto di Core Data e SQLite. Come vedremo di seguito, è estremamente facile da usare.
Per salvare un dato in Realm, dobbiamo prima incapsulare un oggetto derivato dalla classe RLMObject. Quello che dobbiamo fare ora è creare una classe modello che memorizzerà i dati per i singoli articoli. Ecco com'è facile creare una classe del genere.
#import "RLMObject.h" @interface ArticleRealm : RLMObject @property NSString *leadParagraph; @property NSString *url; @end
E questo potrebbe essere praticamente tutto, l'implementazione di questa classe potrebbe rimanere vuota. Si noti che le proprietà nella classe del modello non hanno attributi come non atomico, forte o copia. Realm si prende cura di quelli e non dobbiamo preoccuparci di loro.
Poiché gli articoli che possiamo ottenere sono modellati con il modello Mante Article , sarebbe conveniente inizializzare gli oggetti ArticleRealm con oggetti della classe Article . Per fare ciò, aggiungeremo il metodo initWithMantleModel al nostro modello Realm. Ecco l'implementazione completa della classe ArticleRealm .
#import "RLMObject.h" #import "ArticleModel.h" @interface ArticleRealm : RLMObject @property NSString *leadParagraph; @property NSString *url; - (id)initWithMantleModel:(ArticleModel *)articleModel; @end
#import "ArticleRealm.h" @implementation ArticleRealm - (id)initWithMantleModel:(ArticleModel *)articleModel{ self = [super init]; if(!self) return nil; self.leadParagraph = articleModel.leadParagraph; self.url = articleModel.url; return self; } @end
Interagiamo con il database utilizzando oggetti della classe RLMRealm . Possiamo facilmente ottenere un oggetto RLMRealm invocando il metodo "[RLMRealm defaultRealm]". È importante ricordare che un tale oggetto è valido solo all'interno del thread su cui è stato creato e non può essere condiviso tra thread. Scrivere dati su Realm è abbastanza semplice. È necessario eseguire una singola scrittura, o una serie di esse, all'interno di una transazione di scrittura. Ecco un esempio di scrittura nel database:
RLMRealm *realm = [RLMRealm defaultRealm]; ArticleRealm *articleRealm = [ArticleRealm new]; articleRealm.leadParagraph = @"abc"; articleRealm.url = @"sampleUrl"; [realm beginWriteTransaction]; [realm addObject:articleRealm]; [realm commitWriteTransaction];
Quello che succede qui è il seguente. Per prima cosa creiamo un oggetto RLMRealm per interagire con il database. Quindi viene creato un oggetto modello ArticleRealm (tenere presente che è derivato dalla classe RLMRealm ). Infine, per salvarlo, inizia una transazione di scrittura, l'oggetto viene aggiunto al database e una volta salvato viene eseguito il commit della transazione di scrittura. Come possiamo vedere, le transazioni di scrittura bloccano il thread su cui vengono invocate. Mentre si dice che Realm sia molto veloce, se dovessimo aggiungere più oggetti al database all'interno di una singola transazione sul thread principale, ciò potrebbe comportare la mancata risposta dell'interfaccia utente fino al termine della transazione. Una soluzione naturale è eseguire tale transazione di scrittura su un thread in background.
Richiesta API e risposta persistente in Realm
Queste sono tutte le informazioni di cui abbiamo bisogno per rendere persistenti gli articoli usando Realm. Proviamo a eseguire una richiesta API utilizzando il metodo
- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure
e modelli di richiesta e risposta di Mantle al fine di ottenere articoli del New York Times che avevano qualcosa a che fare (come nell'esempio precedente) con il basket e sono stati pubblicati nei primi sette giorni di giugno 2015. Una volta che l'elenco di tali articoli è disponibile, noi persisterà in Realm. Di seguito è riportato il codice che lo fa. È inserito nel metodo viewDidLoad del controller di visualizzazione tabella nella nostra app.
ArticleListRequestModel *requestModel = [ArticleListRequestModel new]; // (1) requestModel.query = @"Basketball"; requestModel.articlesToDate = [[ArticleListRequestModel dateFormatter] dateFromString:@"20150706"]; requestModel.articlesFromDate = [[ArticleListRequestModel dateFormatter] dateFromString:@"20150701"]; [[APIManager sharedManager] getArticlesWithRequestModel:requestModel // (2) success:^(ArticleListResponseModel *responseModel){ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // (3) @autoreleasepool { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; for(ArticleModel *article in responseModel.articles){ ArticleRealm *articleRealm = [[ArticleRealm alloc] initWithMantleModel:article]; // (4) [realm addObject:articleRealm]; } [realm commitWriteTransaction]; dispatch_async(dispatch_get_main_queue(), ^{ // (5) RLMRealm *realmMainThread = [RLMRealm defaultRealm]; // (6) RLMResults *articles = [ArticleRealm allObjectsInRealm:realmMainThread]; self.articles = articles; // (7) [self.tableView reloadData]; }); } }); } failure:^(NSError *error) { self.articles = [ArticleRealm allObjects]; [self.tableView reloadData]; }];
Innanzitutto, viene effettuata una chiamata API (2) con un modello di richiesta (1), che restituisce un modello di risposta che contiene un elenco di articoli. Per mantenere questi articoli usando Realm, dobbiamo creare oggetti modello Realm, che si svolge nel ciclo for (4). È anche importante notare che, poiché più oggetti sono persistenti all'interno di una singola transazione di scrittura, tale transazione di scrittura viene eseguita su un thread in background (3). Ora, una volta che tutti gli articoli sono stati salvati in Realm, li assegniamo alla proprietà della classe self.articles (7). Poiché sarà possibile accedervi in seguito nel thread principale nei metodi dell'origine dati di TableView, è sicuro recuperarli dal database Realm anche nel thread principale (5). Anche in questo caso, per accedere al database da un nuovo thread, è necessario creare un nuovo oggetto RLMRealm (6) su quel thread.
Se il recupero di nuovi articoli dall'API non riesce per qualsiasi motivo, quelli esistenti vengono recuperati dalla memoria locale nel blocco di errore.
Avvolgendo
In questo tutorial abbiamo imparato a configurare Mantle, un framework modello per Cocoa e Cocoa Touch, per interagire con un'API remota. Abbiamo anche imparato come persistere localmente i dati recuperati sotto forma di oggetti modello Mantle utilizzando il database mobile Realm.
Nel caso in cui desideri provare questa applicazione, puoi recuperare il codice sorgente dal suo repository GitHub. Sarà necessario generare e fornire la propria chiave API prima di eseguire l'applicazione.