Come eliminare NSLog() dai sorgenti XCode

<a target="_blank" href="http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSLog">NSLog()</a> è una funzione utilissima durante le fasi iniziali di un progetto, per il testing e il debug di un’applicazione per Apple iPhone o, più in generale, in ambiente XCode. Essendo appunto una funzione, esattamente come le altre, la sua presenza si farà sentire anche quando rilasceremo (release) il nostro eseguibile. Diventa quindi necessario rimuovere, in qualche modo, tutte le righe di NSLog() dal nostro codice, sia perchè non più necessarie, sia perchè le chiamate a NSLog() potrebbero influire sulle performance della nostra applicazione, soprattutto se abbiamo inserito NSLog() all’interno di loop.

Escludiamo subito la soluzione del “cerca” ed “elimina”; perchè un domani ci potrebbero servire nuovamente. Escludiamo anche la soluzione del “cerca” e “commenta”, scomoda per lo stesso motivo di prima. Fortunatamente una soluzione pulita, semplice e corretta la troviamo sfruttando le istruzioni condizionali del compilatore. Quello che faremo, in pratica, e dire al compilatore di escludere – se si verifica una determinata condizione – durante la compilazione del nostro sorgente le righe che contengono NSLog().

Le direttive di compilazioni e le istruzioni condizionali del compilatore, sono uno strumento molto potente e diffuso. Chi proviene dallo sviluppo ANSI-C le conosce sicuramente molto bene e le avrà utilizzte in moltissime situazioni. La particolarità di queste “istruzioni” risiede nel fatto, sopra accennato, di essere viste dal compilatore e non dall’eseguibile. Questa loro caratteristica le rende utili in moltissimi casi e permette di risolvere problematiche altrimenti assai fastidiose.

Vediamo subito un esempio di codice che, come preannunciato, permette di “eliminare” dalla compilazione parti di codice, nel nostro caso NSLog():

1
2
3
4
5
6
#define ACTIVE_NSLOG 1
// se la costante ACTIVE_NSLOG è definita compila
// il blocco di codice compreso tra #ifdef e #endif
#ifdef ACTIVE_NSLOG
    NSLog( @" ... bla bla" );
#endif

Le istruzioni condizionali del compilatore fanno parte della stessa famiglia della #define; anch’esse, infatti, sono precedute dal carattere “cancelletto” (#). Nell’esempio mostrato abbiamo definito una costante ACTIVE_NSLOG; le successive righe di codice dicono al compilatore di “includere” la riga NSLog() solo se ACTIVE_NSLOG è definito. Se abbiamo avuto la cura, durante la stesura del nostro codice, di inserire le chiamate a NSLog() all’interno del blocco #ifdef ... #endif, basterà eliminare la definizione della costante ACTIVE_NSLOG per far sparire, alla prossima compilazione, tutti i nostri NSLog().

Una soluzione migliore e definitiva

Vediamo adesso come impostare l’ambiente XCode per migliorare ancor di più ciò che abbiamo fatto sopra! Prima di tutto scegliamo un nome di costante che useremo nei nostri progetti per escludere dalla compilazione NSLog(). Potete scegliere il nome che più vi piace, da DEBUG a MIO_DEBUG o quello che preferite. Aprite il vostro progetto, nuovo o vecchio. Inserite tutti gli NSLog() all’interno del blocco (o di un blocco):

1
2
3
#ifdef MIO_DEBUG
    NSLog( @" ... bla bla" );
#endif

Selezionate il file principale del vostro progetto, cliccate con il tasto destro e scegliete la voce Get Info.

getinfo

Si aprira la finestra con le informazioni sul progetto:

userdefine

Selezionate la scheda Build, verificate di essere in configurazione Debug (è questa la chicca), andate nella sezione User-Defined e aggiungete, tramite il bottone in basso a sinistra, un nuovo campo chiamato OTHER_CFLAGS. A questo assegnamoli il valore -DMIO_DEBUG=1. La sintassi è -D{mia define}=1.

Questa procedura ha due vantaggi:

  1. Non dobbiamo inserire nel codice la #define MIO_DEBUG 1, ma lo facciamo tramite le informazioni sul progetto. Quindi, quando andremo a compilare la versione di rilascio (quella senza gli NSLog()) non dobbiamo ricordarci di eliminare la riga #define MIO_DEBUG 1
  2. La costante è definita in relazione alla configurazione, nel nostro caso Debug. Quindi, passando alla configurazione di rilascio (release) la costante sarà assente e le righe con NSLog() non verranno compilate

Conclusioni

La procedura sopra descritta può essere utile in una moltidutine di altri casi che, con NSLog(), non hanno nulla a che fare. Le istruzioni condizionali del compilatore possono aiutarci in una vastissima gamma di contesti. Spesso sono utilizzate dai programmatori per determinare il tipo di sistema operativo, la versione, il target, la presenza di processori matematici, mantenendo uno stesso “identico” sorgente.

Per capirci, come esempio, possiamo utilizzre la nostra costante MIO_DEBUG anche per intervenire in altre zone del codice:

1
2
3
4
5
6
7
8
9
10
// se sono in debug vinco la partita
// con uno score di 100 invece che di 10000 :)
#ifdef MIO_DEBUG
    if( score == 100 )
#else
    if( score == 10000 )
#endif
    {
        [self haiVinto];
    }

Per completare, ecco alcuni esempi e varianti:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// più in generale esiste la
#if espressione
// simile alle if tradizionali, quindi con una espressione completa

// verifica se è definita una costante
#ifdef costante

// verifica se NON è definita una costante
#ifndef costante

// else
#else

// chiusura del blocco
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ad esempio...
#define DEBUG 1
#define MIA_ALTRA_COSTANTE 5
 
...
#if DEBUG
  // compila questo
#else
  // altrimenti compila quest'altro
#endif
 
#if MIA_ALTRA_COSTANTE > 4
  NSLog(@"...");
#endif
1
2
3
4
#ifndef INCLUDE_MIO_FILE
    #define INCLUDE_MIO_FILE
    #include "mio_file.h"
#endif