Debug (modalità di debug) — è una configurazione di build per applicazioni mobili in cui il compilatore include informazioni simboliche, disabilita l'ottimizzazione del codice e collega il debugger per l'analisi passo passo dell'esecuzione. Secondo Android Developers, un build Debug contiene simboli di debug, non comprime le risorse e permette di collegare un ispettore di database e richieste di rete. La modalità Debug si contrappone al build Release: in Debug lo sviluppatore sacrifica le prestazioni per la trasparenza dell'esecuzione del codice.
Punti chiave
Debug non è solo un flag del compilatore, ma un intero insieme di impostazioni che rendono l'applicazione trasparente per lo sviluppatore. In modalità Debug, il compilatore aggiunge una tabella di nomi simbolici (DWARF) al file eseguibile, che collega il codice macchina alle righe di codice sorgente. Senza questa tabella, il debugger non può mostrare quale riga di codice è in esecuzione in quel momento.
Debugger è un programma che esegue la tua applicazione in un ambiente controllato. Puoi mettere in pausa l'esecuzione su qualsiasi riga (breakpoint), visualizzare i valori di tutte le variabili nell'ambito corrente, modificarli al volo e continuare l'esecuzione. Per le piattaforme mobili, il debugger standard è LLDB — un componente LLVM utilizzato sia in Xcode che in Android Studio.
La modalità Debug include anche controlli aggiuntivi che sono disabilitati in Release: asserzioni, controlli dei limiti degli array, rilevatori di perdite di memoria e registrazione estesa. Questi controlli rallentano l'applicazione ma rilevano errori nelle prime fasi dello sviluppo — prima che il codice raggiunga l'utente.
La differenza tra build Debug e Release è fondamentale: sono due diversi insiemi di flag del compilatore, configurazioni di firma e impostazioni di impacchettamento. Comprendere queste differenze aiuta a evitare situazioni in cui “funziona nel simulatore ma non su un dispositivo reale”.
| Parametro | Debug | Release |
|---|---|---|
| Ottimizzazione | Disabilitata (-O0) | Abilitata (-Os o -O2) |
| Simboli | Tabella DWARF completa | Rimossi |
| Firma | Certificato di sviluppo | Certificato di distribuzione |
| Profili | Profilo di provisioning Debug | Profilo App Store / Ad Hoc |
| Registrazione | Completa (tutti i livelli) | Disabilitata o minima |
| Offuscamento | Disabilitato | Abilitato (ProGuard/R8) |
| Dimensione .apk/.ipa | Maggiore (simboli + senza compressione) | Minore (R8 + risorse) |
Build Debug viene utilizzato in tutte le fasi di sviluppo e test su dispositivi locali. Il build Release viene assemblato prima dell'invio ad App Store Connect o Google Play Console. Eseguire il debug su un build Release è tecnicamente possibile ma estremamente scomodo a causa dei metodi rinominati (R8) e della mancanza di symbolication per i log di crash.
Un problema comune è il codice che funziona in Debug ma crasha in Release. La causa è UB (comportamento indefinito) nel codice che il compilatore gestisce diversamente con diversi livelli di ottimizzazione. Un esempio tipico: leggere una variabile non inizializzata o violare lo strict aliasing. Per rilevare questi errori, usa un analizzatore statico (Clang Static Analyzer, ktlint) prima di ogni build Release.
LLDB è un debugger ad alte prestazioni basato su LLVM, che supporta C, C++, Objective-C, Swift e Kotlin/Native. LLDB fornisce un'interfaccia REPL in cui puoi eseguire espressioni arbitrarie, modificare valori di variabili e chiamare funzioni nel contesto di un'applicazione in pausa.
Breakpoint è uno strumento chiave del debugger. Imposti un punto su una riga di codice e l'applicazione si mette in pausa quando l'esecuzione raggiunge quella riga. LLDB supporta diversi tipi di breakpoint: condizionali (si attivano solo quando una condizione è soddisfatta), simbolici (su chiamata di funzione) e monouso (si attivano una volta e vengono rimossi automaticamente).
Watchpoint è un punto di osservazione delle modifiche di una variabile. Specifichi un indirizzo di memoria e il debugger mette in pausa l'esecuzione su qualsiasi scrittura a quell'indirizzo. Questo strumento è indispensabile per trovare race condition e mutazioni errate di oggetti condivisi. Per visualizzare la gerarchia UIKit, usa l'UIView Inspector disponibile in Xcode.
// Impostare breakpoint condizionale
(lldb) breakpoint set --name "viewDidLoad" --condition "self.isViewLoaded == false"
// Watchpoint su proprietà
(lldb) watchpoint set variable self->_loadingState
// Eseguire codice nel contesto sospeso
(lldb) expr self.view.backgroundColor = UIColor.redColor
Entrambi gli IDE forniscono ispettori grafici sopra LLDB. Android Studio include Layout Inspector (gerarchia delle viste), Network Inspector (tracciamento delle richieste HTTP) e Database Inspector (SQLite in tempo reale). Xcode fornisce Debug Memory Graph (analisi perdite di memoria) e View Debugger (visualizzazione 3D dei layer UIKit).
A partire da Android 11, il debug via Wi-Fi funziona senza connessione USB: basta scansionare il codice QR da Android Studio. iOS supporta il debug Wi-Fi da Xcode 9+ — il dispositivo si connette una volta via USB, dopodiché le sessioni di debug possono funzionare in rete. Il debug Wi-Fi non è adatto per server CI a causa della latenza imprevedibile e della perdita di pacchetti, quindi le pipeline automatizzate usano sempre USB. Tuttavia, per lo sviluppo locale, il debug Wi-Fi è notevolmente più comodo — lo sviluppatore non è legato a un cavo e può testare l'applicazione su un dispositivo all'altra estremità della stanza.
Android Debug Bridge (ADB) è uno strumento universale per interagire con un dispositivo Android dalla riga di comando. Tramite ADB puoi installare un'applicazione, avviare il debug, copiare file, eseguire comandi shell e visualizzare i log. Android Studio usa ADB internamente per tutte le operazioni di debug.
Android Studio supporta due modalità di debug: Run (avvio normale) e Debug (avvio con debugger collegato). In modalità Debug puoi impostare breakpoints direttamente nell'editor, ispezionare variabili nella finestra Debug Tool e valutare espressioni in Evaluate Expression. Per eseguire il debug di processi in background (Service, BroadcastReceiver), usa Attach Debugger to Android Process.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Breakpoint qui sospenderà l'esecuzione
val button = findViewById<Button>(R.id.btn_debug)
button.setOnClickListener {
startDebugProcess()
}
}
private fun startDebugProcess() {
val data = fetchDataFromApi()
Log.d("Debug", "Data loaded: $data")
}
}
I comandi shell ADB forniscono accesso al filesystem del dispositivo senza privilegi di root. Puoi visualizzare il contenuto della directory databases, copiare il file .db sul tuo computer e aprirlo con qualsiasi client SQLite. Android Studio Database Inspector automatizza questo processo: vedi i dati del database in tempo reale e puoi eseguire query SQL direttamente dall'IDE.
Xcode fornisce un ambiente di debug integrato basato su LLDB. Lo sviluppatore può eseguire l'applicazione su un simulatore o dispositivo fisico, impostare breakpoints e usare il Debug Navigator per controllare i thread di esecuzione. A differenza di Android, iOS non permette di eseguire due build Debug contemporaneamente sullo stesso dispositivo senza configurazione speciale.
Il simulatore esegue l'applicazione come processo nativo macOS, offrendo il ciclo di debug più veloce. Su un dispositivo fisico, il debug avviene via USB o Wi-Fi (a partire da iOS 16) e LLDB comunica con debugserver sul dispositivo. Le prestazioni di debug sul dispositivo sono inferiori a causa della larghezza di banda limitata di USB 2.0, ma solo un dispositivo fisico permette di testare scenari reali: notifiche push, fotocamera, sensori.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
let label = UILabel()
label.text = "Modalità Debug"
label.textColor = .systemBlue
view.addSubview(label)
}
}
Xcode Organizer raccoglie i log di crash dai dispositivi dei tester tramite Crash Logs. Per la symbolication (conversione degli indirizzi in nomi di funzioni) è necessario il file .dSYM, che viene generato con ogni build Debug. In un build Release, il dSYM viene comunque creato, ma i log di crash dall'App Store devono essere caricati in Organizer manualmente o tramite il servizio bitcode.
Domande frequenti
Tecnicamente sì — tramite distribuzione Ad Hoc con un certificato Debug, ma Apple e Google non lo raccomandano. Un build Debug contiene simboli di debug e prestazioni ridotte, degradando l'UX e aumentando la dimensione dell'applicazione di 2–3 volte.
Il motivo è l'ottimizzazione del compilatore disabilitata (-O0). Il compilatore non inlinea le funzioni, non rimuove il codice morto e conserva tutte le variabili intermedie. Inoltre, Debug include controlli di asserzioni e limiti degli array che sono assenti in Release.
In Xcode scegli Window → Devices and Simulators, spunta “Connect via network” per il tuo dispositivo. Il dispositivo e il Mac devono essere sulla stessa rete Wi-Fi. Dopo la connessione via USB una volta, il debug funzionerà via Wi-Fi nei lanci successivi.
Attach to process permette di collegare il debugger a un processo già in esecuzione senza riavviare l'applicazione. È utile per eseguire il debug di Service, BroadcastReceiver o processi avviati da un evento di sistema, dove il Debug Run standard non è applicabile.
NSLog e print per impostazione predefinita emettono log solo nella configurazione Debug. Per Release, usa os_log con il flag OSLogType.default — salva i messaggi nel Unified Logging System ed è accessibile tramite Console.app su Mac.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche