Gestionarea configurațiilor de compilare în Xcode
Manualului lipsă
pentru dezvoltarea Swift
Ghidul pe care mi-aș fi dorit să-l am când am început
Alăturați-vă la 20,000+ de dezvoltatori care învață despre dezvoltarea Swift
Descărcați exemplarul dvs. gratuit
Un proiect Xcode nou definește două configurații de construcție, Debug și Release. Majoritatea proiectelor definesc una sau mai multe configurații de compilare suplimentare din diverse motive. Acest lucru nu este nou și este o bună practică să folosiți configurațiile de compilare pentru a adapta o compilare la nevoile specifice ale mediului în care va fi implementată.
În acest episod, vă arăt cum să gestionați în siguranță datele care sunt specifice unei configurații de compilare, cum ar fi cheile API, acreditările și alte date sensibile. Există mai multe strategii pentru a gestiona configurațiile de compilare, dar există diferențe importante pe care trebuie să le luați în considerare, în special în contextul securității.
Aducerea unei configurații
Porniți Xcode și creați un nou proiect alegând șablonul Single View App din secțiunea iOS > Application.
Numiți proiectul Configurations, setați Language la Swift și asigurați-vă că sunt debifate căsuțele de verificare din partea de jos. Spuneți-i lui Xcode unde doriți să stocați proiectul și faceți clic pe Create.
Deschideți Navigatorul de proiecte din stânga și faceți clic pe proiectul din partea de sus. Selectați proiectul din secțiunea Proiect pentru a afișa detaliile proiectului. Un proiect Xcode nou definește două configurații de construire, Debug și Release.
În mod obișnuit, utilizați configurația Debug în timpul dezvoltării, în timp ce configurația Release este utilizată pentru crearea de construcții App Store sau TestFlight. Probabil că acest lucru nu este nou pentru dumneavoastră.
Multe aplicații comunică cu un backend și este o practică obișnuită să aveți un mediu de staging și un mediu de producție. Mediul de staging este utilizat în timpul dezvoltării. Faceți dublu clic pe configurația Debug și redenumiți-o în Staging.
Să adăugăm o a treia configurație pentru mediul de producție. Faceți clic pe butonul + din partea de jos a tabelului, alegeți Duplicate „Staging” Configuration și denumiți configurația Production.
Să creăm o schemă pentru fiecare configurație pentru a facilita comutarea rapidă între medii. Selectați schema din partea de sus și alegeți Manage Schemes… din meniu. Selectați schema numită Configurations și faceți clic pe ea încă o dată. Redenumiți-o în Staging.
Cu schema selectată, faceți clic pe pictograma în formă de angrenaj din partea de jos și alegeți Duplicate. Numiți schema Producție. Selectați Run în partea stângă și setați Build Configuration la Production.
Aceasta este tot. Acum avem o configurație de construire pentru staging și producție. Schemele fac trecerea rapidă și ușoară între configurațiile de construire.
Setări de construire definite de utilizator
După cum am menționat mai devreme, există mai multe soluții pentru a gestiona datele care sunt specifice unei anumite configurații de construire. În acest episod, prezint o soluție pe care o folosesc în orice proiect care are o anumită complexitate. Înainte de a expune soluția pe care o am în minte, aș dori să vă arăt o altă soluție care este adesea folosită de dezvoltatori.
Alegeți proiectul din Project Navigator din stânga. Selectați ținta Configurations din secțiunea Targets și faceți clic pe fila Build Settings din partea de sus.
Fila Build Settings arată setările de construire pentru ținta Configurations. Este posibil să extindeți această listă cu setările de construire pe care le definiți dumneavoastră. Faceți clic pe butonul + din partea de sus și alegeți Add User-Defined Setting (Adăugare setare definită de utilizator).
Denumiți setarea definită de utilizator BASE_URL. Definirea unui URL de bază este obișnuită pentru aplicațiile care interacționează cu un backend.
Care este avantajul definirii unei setări definite de utilizator? Setarea definită de utilizator ne permite să stabilim o valoare pentru BASE_URL pentru fiecare configurație de construire. Faceți clic pe triunghiul din stânga setării definite de utilizator pentru a afișa lista de configurații de construire.
Setați valoarea pentru Production and Release la https://cocoacasts.com și valoarea pentru Staging la https://staging.cocoacasts.com.
Așa cum sugerează și numele, o setare de construire este disponibilă în timpul procesului de construire. Codul dvs. nu poate accesa direct setările de construire pe care le definiți. Aceasta este o concepție greșită frecventă. Totuși, există o soluție. Deschideți Info.plist și adăugați o nouă pereche cheie/valoare. Setați cheia la BASE_URL și valoarea la $(BASE_URL)
.
Ce valoare are adăugarea unei perechi cheie/valoare în Info.plist al proiectului? Valoarea perechii cheie/valoare este actualizată în momentul compilării. Xcode inspectează setările de construcție pentru configurația de construcție curentă și stabilește valoarea cheii BASE_URL din Info.plist al proiectului.
Să încercăm. Deschideți AppDelegate.swift și navigați la metoda application(_:didFinishLaunchingWithOptions:)
. Trebuie să accesăm conținutul fișierului Info.plist. Acest lucru este posibil prin intermediul proprietății infoDictionary
a clasei Bundle
. Pachetul care ne interesează este pachetul principal. După cum sugerează și numele, proprietatea infoDictionary
este un dicționar de perechi cheie/valoare, iar noi accesăm valoarea pentru cheia BASE_URL
.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Bundle.main.infoDictionary?) return true}
Selectați schema Production din partea de sus și rulați aplicația în simulator. Inspectați ieșirea în consolă.
Optional(https://staging.cocoacasts.com)
Optional(https://staging.cocoacasts.com)
Selectați schema Staging în partea de sus și rulați aplicația în simulator. Inspectați ieșirea în consolă. Este destul de frumos. Nu-i așa?
Optional(https://staging.cocoacasts.com)
Un cuvânt despre securitate
Majoritatea dezvoltatorilor găsesc această soluție foarte convenabilă. Și chiar este. Există totuși o problemă. Este ușor de extras fișierul Info.plist din aplicațiile descărcate de pe App Store. Ce se întâmplă dacă stocați chei API, credențiale sau alte informații sensibile în fișierul Info.plist? Acest lucru introduce un risc de securitate semnificativ pe care putem și ar trebui să îl evităm.
Am dori să vă arăt o soluție simplă care oferă aceeași flexibilitate, siguranță de tip și securitate îmbunătățită. Creați un nou fișier Swift și numiți-l Configuration.swift.
Definiți un enum cu numele Configuration și o valoare brută de tip String
. Definim un caz pentru fiecare configurație de compilare, staging
, production
și release
.
import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release}
Înainte de a continua cu implementarea enumului Configuration, trebuie să actualizăm fișierul Info.plist. Deschideți Info.plist și adăugați o pereche cheie/valoare. Cheia este Configuration, iar valoarea este $(CONFIGURATION)
. Valoarea CONFIGURATION este setată automat pentru noi. Nu trebuie să ne facem griji în legătură cu ea. După cum sugerează și numele, valoarea este egală cu numele configurației de compilare cu care este creată compilarea.
Revizitați Configuration.swift. Dorim să avem acces ușor la configurația de construire, valoarea stocată în fișierul Info.plist al proiectului. Definiți o proprietate statică, constantă, current
, de tip Configuration
. Accesăm valoarea care este stocată în fișierul Info.plist pentru cheia Configuration și o transformăm într-o instanță String
. Aruncăm o eroare fatală dacă acest lucru eșuează, deoarece acest lucru nu ar trebui să se întâmple niciodată.
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") } }()}
Utilizăm valoarea din fișierul Info.plist pentru a crea o instanță Configuration
. Aruncăm o altă eroare fatală dacă inițializarea eșuează. Observați că scriem cu minusculă valoarea stocată în fișierul Info.plist. Instanța Configuration
este returnată de la închidere. Nu uitați să adăugați o pereche de paranteze la închidere.
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 }()}
Să o încercăm. Deschideți AppDelegate.swift și imprimați configurația curentă. Selectați schema Staging, rulați aplicația și inspectați ieșirea în consolă.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.current) return true}
staging
Extinderea configurației
Să știți probabil că îmi place să valorific enums pentru a crea spații de nume. Permiteți-mi să vă arăt cum putem îmbunătăți implementarea actuală a enumului Configuration
. Cea mai simplă soluție este de a defini o proprietate statică, calculată, baseURL
, de tip URL
. De asemenea, puteți defini proprietatea ca o proprietate statică, constantă. Aceasta este o alegere personală. Utilizăm o instrucțiune switch
pentru a returna URL-ul de bază pentru fiecare configurație de construire.
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")! } }}
Există câteva detalii pe care aș dori să le subliniez. În primul rând, nu folosesc un caz default
. Sunt explicit cu privire la valoarea care este returnată pentru fiecare configurație de construire. Acest lucru facilitează identificarea problemelor și face codul mai ușor de citit și mai intuitiv. În al doilea rând, folosesc semnul exclamării pentru a forța desfacerea valorii returnate de inițializatorul structurii URL
. Acesta este unul dintre rarele scenarii în care folosesc semnul exclamării pentru a forța desfacerea unei valori. Este convenabil, dar, mai important, URL-ul de bază nu ar trebui să fie niciodată egal cu nil
.
Deschideți AppDelegate.swift și imprimați valoarea proprietății calculate baseURL
. Cu schema setată pe Staging, rulați aplicația și inspectați rezultatul în consolă.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.baseURL) return true}
https://staging.cocoacasts.com
Acest model are câteva avantaje față de prima soluție pe care am implementat-o. Profităm de siguranța tipurilor din Swift și nu mai avem de-a face cu o valoare opțională. De asemenea, putem beneficia de autocompletarea din Xcode. Aceste avantaje sunt subtile, dar ajungi să le apreciezi în timp.
Să punem cireașa de pe tort prin adăugarea spațiilor de nume la amestec. Creați un nou fișier Swift și numiți-l Configuration+DarkSky.swift.
Creați o extensie pentru enum-ul Configuration și definiți un enum cu numele DarkSky
în extensie. Dark Sky este un serviciu meteo pe care îl folosesc din când în când.
import Foundationextension Configuration { enum DarkSky { }}
Enum-ul DarkSky
definește o proprietate statică, constantă, apiKey
, de tip String
. Comutăm pe configurația curentă și returnăm o valoare diferită pentru fiecare configurație de construcție. După cum am menționat mai devreme, puteți, de asemenea, să declarați proprietatea ca o proprietate statică, variabilă. Depinde de dumneavoastră să decideți acest lucru.
import Foundationextension Configuration { enum DarkSky { static let apiKey: String = { switch Configuration.current { case .staging: return "123" case .production: return "456" case .release: return "789" } }() }}
Există mai multe avantaje ale acestei abordări. Configurația pentru API-ul Dark Sky este frumos namespaced. De asemenea, acest lucru facilitează punerea configurației pentru API Dark Sky într-un fișier separat.
Deschideți AppDelegate.swift și imprimați cheia API pentru serviciul meteo Dark Sky. Cu schema setată pe Staging, rulați aplicația și inspectați ieșirea în consolă.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.DarkSky.apiKey) return true}
123
Ce urmează?
Acest model adaugă comoditate, siguranță de tip și securitate proiectelor dumneavoastră. Este ușor de adoptat și, odată implementat, este simplu de extins. Este un model pe care îmi place să îl folosesc cu plăcere pentru o serie de motive. Vă recomand cu tărie să îl încercați.
.