Debugging in iOS - Suggerimenti essenziali

Sia che tu stia cercando di capire perché il tuo array ha 3 oggetti invece di 5 o perché il tuo gioco gioca all'indietro da quando il nuovo ragazzo è partito, il debugging è una parte essenziale del processo di sviluppo. Alla fine di questo articolo, avrai una comprensione delle più importanti funzioni di debug disponibili e come usarle per aiutare a sconfiggere i bug in minor tempo.

Ci occuperemo di come:

  • Controlla lo stato delle tue applicazioni utilizzando la console
  • Eseguire la registrazione e andare oltre NSLog
  • Traccia l'utilizzo della memoria seguendo i cicli di vita dell'oggetto

Ispezione Usando la console

Quella piccola scatola nera nella parte inferiore di Xcode dovrebbe essere la tua migliore amica quando si tratta di eseguire il debug. Emette messaggi di log, messaggi di errore e tutti i tipi di altre cose utili che ti aiuteranno a tenere traccia degli errori. Oltre a leggere l'output direttamente dal log, possiamo anche fermarci in un dato punto del nostro programma e ispezionare varie parti della nostra applicazione.

Rottura in condizioni

Presumo che tu sappia come funzionano i breakpoint (e se non lo fai, non preoccuparti, lo raccoglierai alla fine di questo!). I punti di rottura hanno un valore inestimabile nel vedere dove si trova la nostra applicazione in un dato momento, ma può essere doloroso passare attraverso un ciclo o una funzione ricorsiva dopo aver innescato un punto di interruzione finché il nostro oggetto non è uguale a un determinato valore. Inserire i breakpoint condizionali!

I breakpoint condizionali sono breakpoint che si interrompono solo quando viene soddisfatta una determinata condizione. Immagina di voler interrompere solo quando un oggetto si trova in un determinato stato o durante l'ennesima iterazione di un ciclo. Aggiungi un punto di interruzione al tuo codice facendo clic sul "gutter" dell'editor Xcode, fai clic con il pulsante destro del mouse sul punto di interruzione e quindi seleziona "modifica punto di interruzione" per impostare condizioni speciali.

È possibile fornire una condizione (ad esempio i == 12) o il numero di volte in cui il punto di interruzione deve essere ignorato. È anche possibile aggiungere azioni che si verificano automaticamente durante l'interruzione, ad esempio un comando debugger che stampa un valore.

Mancia: La scorciatoia da tastiera per aggiungere / rimuovere un punto di interruzione è Comando + \

Un altro importante trucco da implementare è l'aggiunta del "punto di rottura dell'eccezione". Abbiamo notato che il 99% delle volte in cui abbiamo riscontrato un'eccezione, Xcode ci porta al pool di autorelease nel nostro metodo principale?


Grazie Xcode ... davvero utile!

Impostando un punto di interruzione di eccezione, è possibile specificare la riga esatta del codice che ha causato l'eccezione con un punto di interruzione. Per fare ciò, aprire la scheda del punto di interruzione delle eccezioni (comando + 6). In basso a sinistra della finestra, c'è un pulsante "+". Seleziona questo per aggiungere un 'punto di interruzione di eccezione'. Ora, quando Xcode incontra un'eccezione, interromperà la posizione nel codice che si è verificato.

Stampa manuale dalla console

Se abbiamo rotto in un punto specifico della nostra app in generale, è perché vogliamo vedere in che stato sono i nostri oggetti. Xcode ci fornisce la 'vista delle variabili' che è quella vista nella parte inferiore di Xcode accanto alla console. In teoria mostra lo stato corrente di tutti i valori rilevanti nel contesto attuale. In pratica, questo a volte si rivela un po 'buggato. A volte non elenca i valori o non li aggiorna quando passi.

Fortunatamente, possiamo ispezionare oggetti specifici usando alcuni comandi della console molto utili. Digitare 'po' nella console ci consente di ottenere dettagli istantanei su un dato oggetto (quando si tratta di valori scalari usiamo 'p').

Questo può essere utile per vedere se un oggetto esiste già (questo verrà stampato zero in caso contrario, determinare il valore di un oggetto, scoprire cosa contiene un array / dizionario in fase di esecuzione e persino confrontare due oggetti. Poiché questo comando stampa l'indirizzo di memoria dell'oggetto pertinente, puoi stampare due oggetti che ritieni debbano essere gli stessi e vedere se hanno lo stesso indirizzo di memoria per essere sicuri.

Un altro comando utile ma nascosto che puoi usare per ispezionare facilmente le tue visualizzazioni è il recursiveDescription comando. Chiamalo in una vista per ottenere una stampa dalla sua gerarchia di visualizzazione.


Registrazione efficace

Ci sono certe volte durante il debug del nostro programma che vogliamo registrare un certo messaggio nella console. La funzione 'NSLog' ci consente di stampare qualsiasi output vogliamo alla console. Questo può essere importante se vogliamo seguire determinati percorsi attraverso la nostra applicazione o testare quale valore è uguale senza dover posizionare esplicitamente i punti di interruzione su ogni linea possibile. NSLog segue lo stesso formato di [NSString StringWithFormat] metodo (come puoi vedere nella scorciatoia in basso).

Mancia: Nuovo per formattare le stringhe in Objective-C? Scopri la guida alla programmazione delle stringhe di mele

Ottimizzare con NSLog

Mentre NSLog è utile, dobbiamo essere intelligenti su come lo implementiamo. Tutto ciò che viene stampato da NSLog entra nel codice di produzione ed è quindi visibile a chiunque. Se qualcuno doveva collegare il dispositivo all'organizzatore e guardare la console, potevano vedere ogni singolo messaggio di registro. Come puoi immaginare, questo potrebbe avere delle serie implicazioni! Immagina se hai stampato una logica algoritmica segreta o la password dell'utente nella console! Per questo motivo, a volte le app vengono rifiutate se Apple rileva troppi output sulla console in una build di produzione.

Fortunatamente, c'è un modo migliore per fare il logging. A seconda di quanta fatica vuoi mettere, ci sono diversi modi per farlo. Forse il modo più semplice è usare una macro che include solo NSLog nelle build di debug. Includere questo in un file di intestazione accessibile a livello globale significa che puoi inserire tutti i log che vuoi nel tuo codice o che nessuno di questi entrerà in produzione (a patto di non modificare i valori predefiniti di macro pre-processore e se non lo sai cosa sono, non preoccuparti).

 #ifdef DEBUG #define DMLog (...) NSLog (@ "% s% @", __PRETTY_FUNCTION__, [NSString stringWithFormat: __ VA_ARGS__]) #else #define DMLog (...) do  while (0)

Ora se usi DMLog (o qualunque cosa tu scelga di chiamare il tuo) lo stamperà solo durante una build di debug. Qualsiasi build di produzione non farà nulla. __PRETTY_FUNCTION__ aiuta anche a stampare il nome della funzione da cui proviene la registrazione.

Fare il passo successivo

Sebbene NSLog sia ottimo, ha un numero di limitazioni:

  • Stampa solo localmente
  • Non puoi dare un log a 'livello'. (Ad esempio, critico, avviso, tutto, ecc.)
  • NSLog è lento. Può rallentare notevolmente il programma quando si esegue una grande quantità di elaborazione

Per coloro che vogliono avere un po 'di hardcore sul logging, ci sono framework che superano alcune o tutte queste limitazioni a seconda di quanto sforzo si vuole mettere. Consiglierei di guardare i seguenti strumenti:

  • Cocoa LumberJack - Uno dei framework di registrazione ben noti e versatili per Cocoa. Un po 'una curva di apprendimento ma è molto potente
  • SNlog - Un calo nella sostituzione di NSLog

A seguito di cicli di vita dell'oggetto

Sebbene l'introduzione di Automatic Reference Counting (ARC) abbia assicurato che la gestione della memoria non è il vampiro di massa del tempo in cui era, è comunque importante tenere traccia degli eventi importanti nei cicli di vita del nostro oggetto. Dopotutto, ARC non elimina la possibilità di perdite di memoria o tenta di accedere a un oggetto rilasciato (rende semplicemente più difficile farlo). A tal fine, possiamo implementare una serie di processi e strumenti per aiutarci a tenere d'occhio ciò che i nostri oggetti stanno facendo.

Registrazione di eventi importanti

I due metodi più importanti nel ciclo di vita di un oggetto Objective-C sono i dentro e dealloc metodi. È una buona idea registrare questi eventi sulla tua console in modo da poter vedere quando i tuoi oggetti prendono vita e, cosa più importante, assicurarsi che se ne vadano quando dovrebbero.

 - (id) init self = [super init]; if (self) NSLog (@ "% @:% @", NSStringFromSelector (_cmd), self);  return self;  - (void) dealloc NSLog (@ "% @:% @", NSStringFromSelector (_cmd), self); 

Mentre digitando questo codice può sembrare noioso iniziare, ci sono modi per automatizzare il processo e renderlo più semplice. Posso garantire che sarà utile quando l'applicazione si comporta in modi che non dovrebbe. Puoi anche utilizzare alcuni trucchi che hai appreso nella sezione di registrazione, in modo che questo non venga stampato in una build di produzione (o ancora meglio, creane una macro tu stesso!).

L'analizzatore statico e l'ispettore

Ci sono due strumenti forniti con Xcode che possiamo usare per ripulire il nostro codice e rendere il nostro codice meno incline agli errori. Lo strumento Static Analyzer è un modo elegante per Xcode di raccomandare miglioramenti al nostro codice, dagli oggetti non utilizzati agli oggetti potenzialmente sotto o sopra rilasciati (ancora un problema su ARC per gli oggetti Core Foundation). Per vedere questi consigli, vai su Prodotto e seleziona "Anlayze".

L'ispettore è uno strumento potente che ci consente di "ispezionare" da vicino i vari aspetti della nostra applicazione per quanto riguarda l'utilizzo della memoria, l'attività sul file system e fornisce persino dei modi per automatizzare l'interazione dell'interfaccia utente. Per "ispezionare" la tua applicazione seleziona "Profilo" nel menu a discesa "Prodotto".

Ciò aprirà la finestra Strumenti in cui è possibile selezionare un modello di profilo da eseguire. I più comuni da eseguire sono gli zombi (ne parleremo più avanti), il monitoraggio dell'attività e le perdite. Leaks è forse il modello più utile da eseguire per rintracciare eventuali perdite di memoria che possono essere presenti nella vostra applicazione.

Zombies sono tuoi amici

Anche se è molto più difficile imbattersi nel temuto errore EXC_BAD_ACCESS ora che ARC è a posto, può ancora succedere in determinate circostanze. Quando ci occupiamo di UIPopoverController o di oggetti di base del core, possiamo ancora tentare di accedere a un oggetto rilasciato. Normalmente, quando rilasciamo un oggetto in memoria, è sparito per sempre. Tuttavia, quando gli zombi sono abilitati, l'oggetto viene contrassegnato come rilasciato ma rimane in memoria. In questo modo quando accediamo a un oggetto Zombie, Xcode può farci sapere che hai provato ad accedere ad un oggetto che normalmente non ci sarebbe. Perché sa ancora di cosa si tratta, può farti sapere dove e quando è successo.

Puoi cacciare gli zombi usando due metodi. O usando l'ispettore ed eseguendo un modello di profilo Zombie, oppure abilitandolo come opzione diagnostica all'interno dell'opzione di sviluppo 'Esegui'. Proprio accanto al pulsante stop, fai clic sul nome dello schema, quindi su "Modifica schema" e fai clic con il pulsante destro del mouse sulla scheda diagnostica e seleziona "Abilita oggetti Zombie". Si noti che il debug in modalità Zombie è disponibile solo durante il debug nel simulatore, non è possibile farlo su un dispositivo reale.


Conclusione

Si spera che quanto sopra vi ha fornito alcune informazioni su come eseguire il debug delle applicazioni in modo più efficace. Si tratta di trovare modi per ridurre il tempo impiegato per correggere i bug, in modo da poter dedicare più tempo a fare ciò che è importante, creando app di qualità!

Questo non è affatto un elenco completo. Esistono molte altre tecniche che non sono state discusse qui, come problemi di debug in produzione, segnalazione di bug in remoto, rapporti sugli arresti anomali e altro. Hai una tecnica che vuoi condividere? Forse hai una domanda relativa a uno dei punti sopra? Pubblicalo sotto nei commenti!

Buona programmazione!