Articles

Gestire le configurazioni di build in Xcode

Risorse

Scarica la tua copia gratuita di
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Sviluppatori che imparano lo sviluppo Swift

Scarica la tua copia gratuita

Un progetto Xcode nuovo di zecca definisce due configurazioni di build, Debug e Release. La maggior parte dei progetti definisce una o più configurazioni di build aggiuntive per vari motivi. Questo non è nuovo ed è una buona pratica usare le configurazioni di build per adattare una build alle esigenze specifiche dell’ambiente in cui verrà distribuita.

In questo episodio, vi mostro come gestire in modo sicuro i dati che sono specifici per una configurazione di build, come chiavi API, credenziali e altri dati sensibili. Ci sono diverse strategie per gestire le configurazioni di build, ma ci sono importanti differenze che devi considerare, specialmente nel contesto della sicurezza.

Aggiungere una configurazione

Accendi Xcode e crea un nuovo progetto scegliendo il modello Single View App dalla sezione iOS > Application.

Setting Up the Project

Nomina il progetto Configurations, imposta Language a Swift, e assicurati che le caselle di controllo in basso siano deselezionate. Dite a Xcode dove volete memorizzare il progetto e cliccate su Create.

Setting Up the Project

Aprite il Project Navigator a sinistra e cliccate sul progetto in alto. Seleziona il progetto nella sezione Project per mostrare i dettagli del progetto. Un progetto Xcode nuovo di zecca definisce due configurazioni di build, Debug e Release.

Build Configurations in Xcode

Di solito usi la configurazione Debug durante lo sviluppo mentre la configurazione Release è usata per creare build di App Store o TestFlight. Questo probabilmente non è nuovo per te.

Molte applicazioni comunicano con un backend ed è una pratica comune avere un ambiente di staging e uno di produzione. L’ambiente di staging è usato durante lo sviluppo. Fare doppio clic sulla configurazione Debug e rinominarla in Staging.

Build Configurations in Xcode

Aggiungiamo una terza configurazione per l’ambiente di produzione. Fare clic sul pulsante + in fondo alla tabella, scegliere Duplicate “Staging” Configuration e nominare la configurazione Production.

Duplicating a Build Configuration

Duplicating a Build Configuration

Creiamo uno schema per ogni configurazione per rendere facile il passaggio rapido da un ambiente all’altro. Seleziona lo schema in alto e scegli Manage Schemes… dal menu. Seleziona lo schema chiamato Configurations e clicca ancora una volta. Rinominalo in Staging.

Update Schemes

Con lo schema selezionato, clicca sull’icona dell’ingranaggio in basso e scegli Duplica. Dai un nome allo schema Production. Seleziona Run sulla sinistra e imposta Build Configuration su Production.

Update Schemes

Ecco fatto. Ora abbiamo una configurazione di build per la messa in scena e la produzione. Gli schemi rendono facile e veloce passare da una configurazione di build all’altra.

Impostazioni di build definite dall’utente

Come ho detto prima, ci sono diverse soluzioni per gestire i dati che sono specifici di una particolare configurazione di build. In questo episodio, mostro una soluzione che uso in qualsiasi progetto che abbia una certa complessità. Prima di esporre la soluzione che ho in mente, vorrei mostrarvi un’altra soluzione che è spesso usata dagli sviluppatori.

Scegliete il progetto nel Project Navigator a sinistra. Seleziona il target Configurations dalla sezione Targets e clicca sulla scheda Build Settings in alto.

Target Build Settings

La scheda Build Settings mostra le impostazioni di build per il target Configurations. È possibile espandere questa lista con le impostazioni di compilazione che si definiscono. Fai clic sul pulsante + in alto e scegli Add User-Defined Setting.

Adding a User-Defined Setting

Nomina l’impostazione definita dall’utente BASE_URL. Definire un URL di base è comune per le applicazioni che interagiscono con un backend.

Adding a User-Defined Setting

Qual è il vantaggio di definire un’impostazione definita dall’utente? L’impostazione definita dall’utente ci permette di impostare un valore per BASE_URL per ogni configurazione di build. Fai clic sul triangolo a sinistra dell’impostazione definita dall’utente per mostrare l’elenco delle configurazioni di compilazione.

Adding a User-Defined Setting

Imposta il valore per Production e Release a https://cocoacasts.com e il valore per Staging a https://staging.cocoacasts.com.

Adding a User-Defined Setting

Come implica il nome, un’impostazione di compilazione è disponibile durante il processo di compilazione. Il tuo codice non può accedere direttamente alle impostazioni di compilazione che definisci. Questo è un malinteso comune. C’è una soluzione, però. Aprite Info.plist e aggiungete una nuova coppia chiave/valore. Imposta la chiave su BASE_URL e il valore su $(BASE_URL).

Updating Info.plist

Che valore ha aggiungere una coppia chiave/valore a Info.plist del progetto? Il valore della coppia chiave/valore viene aggiornato al momento della compilazione. Xcode ispeziona le impostazioni di compilazione per la configurazione corrente e imposta il valore della chiave BASE_URL in Info.plist del progetto.

Proviamolo. Apriamo AppDelegate.swift e passiamo al metodo application(_:didFinishLaunchingWithOptions:). Dobbiamo accedere al contenuto del file Info.plist. Questo è possibile attraverso la proprietà infoDictionary della classe Bundle. Il bundle che ci interessa è il bundle principale. Come il nome implica, la proprietà infoDictionary è un dizionario di coppie chiave/valore e noi accediamo al valore della chiave BASE_URL.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Bundle.main.infoDictionary?) return true}

Selezionate lo schema Production in alto ed eseguite l’applicazione nel simulatore. Controlla l’output nella console.

Optional(https://staging.cocoacasts.com)

Seleziona lo schema Staging in alto ed esegui l’applicazione nel simulatore. Ispeziona l’output nella console. Questo è abbastanza bello. Giusto?

Optional(https://staging.cocoacasts.com)

Una parola sulla sicurezza

Molti sviluppatori trovano questa soluzione molto conveniente. E lo è. C’è un problema, però. È facile estrarre il file Info.plist dalle applicazioni scaricate dall’App Store. Cosa succede se si memorizzano chiavi API, credenziali o altre informazioni sensibili nel file Info.plist? Questo introduce un significativo rischio per la sicurezza che possiamo e dobbiamo evitare.

Vorrei mostrarvi una semplice soluzione che offre la stessa flessibilità, sicurezza dei tipi e maggiore sicurezza. Create un nuovo file Swift e chiamatelo Configuration.swift.

Creating a Swift File

Creating a Swift File

Definiamo un enum con nome Configuration e un valore grezzo di tipo String. Definiamo un caso per ogni configurazione di build, staging, production e release.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release}

Prima di continuare con l’implementazione dell’enum Configuration, dobbiamo aggiornare il file Info.plist. Aprite Info.plist e aggiungete una coppia chiave/valore. La chiave è Configuration e il valore è $(CONFIGURATION). Il valore di CONFIGURAZIONE è impostato automaticamente per noi. Non abbiamo bisogno di preoccuparcene. Come il nome implica, il valore è uguale al nome della configurazione di build con cui viene creata la build.

Update Info.plist

Rivisita Configuration.swift. Vogliamo un facile accesso alla configurazione della build, il valore memorizzato nel file Info.plist del progetto. Definiamo una proprietà statica e costante, current, di tipo Configuration. Accediamo al valore che è memorizzato nel file Info.plist per la chiave Configuration e lo fondiamo in un’istanza String. Lanciamo un errore fatale se questo fallisce perché non dovrebbe mai accadere.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } }()}

Utilizziamo il valore dal file Info.plist per creare un’istanza Configuration. Lanciamo un altro errore fatale se l’inizializzazione fallisce. Notate che il valore memorizzato nel file Info.plist è in minuscolo. L’istanza Configuration viene restituita dalla chiusura. Non dimenticate di aggiungere un paio di parentesi alla chiusura.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } guard let configuration = Configuration(rawValue: rawValue.lowercased()) else { fatalError("Invalid Configuration") } return configuration }()}

Proviamola. Aprite AppDelegate.swift e stampate la configurazione attuale. Selezionate lo schema Staging, eseguite l’applicazione e ispezionate l’output nella console.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.current) return true}
staging

Extending Configuration

Probabilmente sapete che mi piace sfruttare gli enum per creare i namespace. Lasciate che vi mostri come possiamo migliorare l’attuale implementazione dell’enum Configuration. La soluzione più semplice è definire una proprietà statica e calcolata, baseURL, di tipo URL. Potete anche definire la proprietà come una proprietà statica e costante. Questa è una scelta personale. Usiamo una dichiarazione switch per restituire l’URL di base per ogni configurazione di build.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } guard let configuration = Configuration(rawValue: rawValue.lowercased()) else { fatalError("Invalid Configuration") } return configuration }() // MARK: - Base URL static var baseURL: URL { switch current { case .staging: return URL(string: "https://staging.cocoacasts.com")! case .production, .release: return URL(string: "https://cocoacasts.com")! } }}

Ci sono diversi dettagli che vorrei sottolineare. Primo, non uso un caso default. Sono esplicito sul valore che viene restituito per ogni configurazione di build. Questo rende più facile individuare i problemi e rende il codice più leggibile e intuitivo. In secondo luogo, uso il punto esclamativo per forzare lo scarto del valore restituito dall’inizializzatore della struct URL. Questo è uno dei rari scenari in cui uso il punto esclamativo per forzare lo scarto di un valore. È conveniente, ma, cosa più importante, l’URL di base non dovrebbe mai essere uguale a nil.

Apri AppDelegate.swift e stampa il valore della proprietà calcolata baseURL. Con lo schema impostato su Staging, eseguite l’applicazione e ispezionate l’output nella console.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.baseURL) return true}
https://staging.cocoacasts.com

Questo schema ha alcuni vantaggi rispetto alla prima soluzione che abbiamo implementato. Approfittiamo della sicurezza dei tipi di Swift e non abbiamo più a che fare con un valore opzionale. Possiamo anche beneficiare del completamento automatico di Xcode. Questi vantaggi sono sottili, ma li si apprezza col tempo.

Mettiamo la ciliegina sulla torta aggiungendo i namespace al mix. Create un nuovo file Swift e chiamatelo Configuration+DarkSky.swift.

Creating a Swift File

Creating a Swift File

Create un’estensione per l’enum Configuration e definite un enum con nome DarkSky nell’estensione. Dark Sky è un servizio meteo che uso di tanto in tanto.

import Foundationextension Configuration { enum DarkSky { }}

L’enum DarkSky definisce una proprietà statica e costante, apiKey, di tipo String. Si passa alla configurazione corrente e si restituisce un valore diverso per ogni configurazione di build. Come ho detto prima, potete anche dichiarare la proprietà come una proprietà statica e variabile. Sta a voi decidere.

import Foundationextension Configuration { enum DarkSky { static let apiKey: String = { switch Configuration.current { case .staging: return "123" case .production: return "456" case .release: return "789" } }() }}

Ci sono diversi vantaggi in questo approccio. La configurazione per l’API di Dark Sky è ben posizionata con i nomi. Questo rende anche facile mettere la configurazione per l’API Dark Sky in un file separato.

Apri AppDelegate.swift e stampa la chiave API per il servizio meteo Dark Sky. Con lo schema impostato su Staging, eseguite l’applicazione e ispezionate l’output nella console.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.DarkSky.apiKey) return true}
123

Cosa c’è dopo?

Questo pattern aggiunge convenienza, sicurezza dei tipi e sicurezza ai vostri progetti. È facile da adottare e, una volta implementato, è semplice da estendere. È un pattern che mi piace usare per una serie di ragioni. Vi consiglio vivamente di provarlo.