Articles

Build-configuraties beheren in Xcode

Resources
Download uw gratis exemplaar van
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Mededoen met 20,000+ ontwikkelaars die leren over Swift-ontwikkeling

Download uw gratis exemplaar

Een gloednieuw Xcode-project definieert twee build-configuraties, Debug en Release. De meeste projecten definiëren een of meer extra build configuraties om verschillende redenen. Dit is niet nieuw en het is een goede gewoonte om build-configuraties te gebruiken om een build aan te passen aan de specifieke behoeften van de omgeving waar hij zal worden uitgerold.

In deze aflevering laat ik zien hoe je veilig gegevens kunt beheren die specifiek zijn voor een build-configuratie, zoals API-sleutels, referenties en andere gevoelige gegevens. Er zijn verschillende strategieën om build-configuraties te beheren, maar er zijn belangrijke verschillen waar u rekening mee moet houden, vooral in de context van beveiliging.

Een configuratie toevoegen

Start Xcode op en maak een nieuw project door het sjabloon Single View App te kiezen in de sectie iOS > Application.

Setting Up the Project

Noem het project Configuraties, stel Taal in op Swift en zorg ervoor dat de selectievakjes onderaan zijn uitgeschakeld. Vertel Xcode waar u het project wilt opslaan en klik op Maken.

Setting Up the Project

Open de Projectnavigator aan de linkerkant en klik op het project bovenaan. Selecteer het project in het Project-gedeelte om de projectdetails weer te geven. Een gloednieuw Xcode-project definieert twee build-configuraties, Debug en Release.

Build Configurations in Xcode

De Debug-configuratie wordt meestal gebruikt tijdens de ontwikkeling, terwijl de Release-configuratie wordt gebruikt voor het maken van App Store- of TestFlight-builds. Dit is waarschijnlijk niet nieuw voor u.

Veel applicaties communiceren met een backend en het is gebruikelijk om een staging en een productie omgeving te hebben. De staging omgeving wordt gebruikt tijdens de ontwikkeling. Dubbelklik op de Debug-configuratie en hernoem deze naar Staging.

Build Configurations in Xcode

Laten we een derde configuratie toevoegen voor de productieomgeving. Klik op de knop + onderaan de tabel, kies Duplicate “Staging”-configuratie en geef de configuratie de naam Production.

Duplicating a Build Configuration

Duplicating a Build Configuration

Laten we een schema maken voor elke configuratie om snel te kunnen schakelen tussen omgevingen. Selecteer het schema bovenaan en kies Schema’s beheren… in het menu. Selecteer het schema genaamd Configuraties en klik er nog een keer op. Wijzig de naam in Staging.

Update Schemes

Met het schema geselecteerd, klikt u op het tandwielpictogram onderaan en kiest u Dupliceren. Geef het schema de naam Production. Selecteer Uitvoeren aan de linkerkant en stel Bouwconfiguratie in op Productie.

Update Schemes

Dat is het. We hebben nu een build configuratie voor staging en productie. De schema’s maken het snel en gemakkelijk om te schakelen tussen build-configuraties.

User-Defined Build Settings

Zoals ik al eerder zei, zijn er verschillende oplossingen om gegevens te beheren die specifiek zijn voor een bepaalde build-configuratie. In deze aflevering laat ik een oplossing zien die ik gebruik in elk project dat enige complexiteit heeft. Voordat ik de oplossing uiteenzet die ik in gedachten heb, wil ik je een andere oplossing laten zien die vaak door ontwikkelaars wordt gebruikt.

Kies het project in de Project Navigator aan de linkerkant. Selecteer het Configurations target in de Targets sectie en klik op het Build Settings tabblad bovenaan.

Target Build Settings

Het Build Settings tabblad toont de build instellingen voor het Configurations target. Het is mogelijk om deze lijst uit te breiden met bouwinstellingen die u definieert. Klik op de knop + bovenaan en kies Door gebruiker gedefinieerde instelling toevoegen.

Adding a User-Defined Setting

Noem de door de gebruiker gedefinieerde instelling BASE_URL. Het definiëren van een basis-URL is gebruikelijk voor toepassingen die communiceren met een backend.

Adding a User-Defined Setting

Wat is het voordeel van het definiëren van een door de gebruiker gedefinieerde instelling? Met de door de gebruiker gedefinieerde instelling kunnen we voor elke buildconfiguratie een waarde voor BASE_URL instellen. Klik op het driehoekje links van de door de gebruiker gedefinieerde instelling om de lijst met build-configuraties weer te geven.

Adding a User-Defined Setting

Stel de waarde voor Productie en Release in op https://cocoacasts.com en de waarde voor Staging op https://staging.cocoacasts.com.

Adding a User-Defined Setting

Zoals de naam al aangeeft, is een build-instelling beschikbaar tijdens het build-proces. Uw code heeft geen directe toegang tot de build instellingen die u definieert. Dat is een veel voorkomende misvatting. Er is echter een oplossing. Open Info.plist en voeg een nieuw sleutel/waarde paar toe. Stel de sleutel in op BASE_URL en de waarde op $(BASE_URL).

Updating Info.plist

Wat is de waarde van het toevoegen van een sleutel/waarde-paar aan Info.plist van het project? De waarde van het sleutel/waarde-paar wordt bijgewerkt tijdens het bouwen. Xcode inspecteert de bouwinstellingen voor de huidige bouwconfiguratie en stelt de waarde van de sleutel BASE_URL in Info.plist.

Laten we het eens uitproberen. Open AppDelegate.swift en navigeer naar de application(_:didFinishLaunchingWithOptions:) methode. We moeten toegang krijgen tot de inhoud van het Info.plist bestand. Dit is mogelijk via de infoDictionary eigenschap van de Bundle klasse. De bundel waar we in geïnteresseerd zijn is de hoofdbundel. Zoals de naam al aangeeft, is de infoDictionary eigenschap een woordenboek van sleutel/waarde paren en we krijgen toegang tot de waarde voor de BASE_URL sleutel.

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

Selecteer het Productie schema bovenaan en voer de applicatie uit in de simulator. Inspecteer de uitvoer in de console.

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

Selecteer het Staging schema bovenaan en voer de toepassing in de simulator uit. Inspecteer de uitvoer in de console. Dat ziet er goed uit. Toch?

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

Een woord over beveiliging

De meeste ontwikkelaars vinden deze oplossing erg handig. En dat is het ook. Er is echter één probleem. Het is eenvoudig om het Info.plist bestand uit applicaties te halen die zijn gedownload uit de App Store. Wat gebeurt er als je API-sleutels, referenties of andere gevoelige informatie in het Info.plist bestand opslaat? Dit introduceert een aanzienlijk veiligheidsrisico dat we kunnen en moeten vermijden.

Ik wil je een eenvoudige oplossing laten zien die dezelfde flexibiliteit, type veiligheid, en verbeterde beveiliging biedt. Maak een nieuw Swift-bestand en noem het Configuration.swift.

Creating a Swift File

Creating a Swift File

Definieer een enum met de naam Configuration en een ruwe waarde van het type String. We definiëren een geval voor elke build-configuratie, staging, production, en release.

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

Voordat we verder gaan met de implementatie van de Configuratie-enum, moeten we het bestand Info.plist bijwerken. Open Info.plist en voeg een sleutel/waarde paar toe. De sleutel is Configuration en de waarde is $(CONFIGURATION). De waarde van CONFIGURATION wordt automatisch voor ons ingesteld. We hoeven ons er geen zorgen over te maken. Zoals de naam al aangeeft, is de waarde gelijk aan de naam van de build-configuratie waarmee de build wordt gemaakt.

Update Info.plist

Bezoek Configuration.swift. We willen gemakkelijk toegang tot de configuratie van de build, de waarde die is opgeslagen in het bestand Info.plist van het project. Definieer een statische, constante eigenschap, current, van het type Configuration. We openen de waarde die is opgeslagen in het bestand Info.plist voor de sleutel Configuration en zetten deze om in een instantie String. We gooien een fatale fout als dit mislukt, want dat zou nooit mogen gebeuren.

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") } }()}

We gebruiken de waarde uit het Info.plist bestand om een Configuration instantie te maken. We gooien nog een fatale foutmelding als de initialisatie mislukt. Merk op dat we de waarde die is opgeslagen in het Info.plist bestand kleine letters geven. De Configuration instantie wordt teruggegeven van de sluiting. Vergeet niet om een paar haakjes toe te voegen aan de closure.

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 }()}

Laten we het eens uitproberen. Open AppDelegate.swift en print de huidige configuratie. Selecteer het Staging schema, voer de applicatie uit, en inspecteer de uitvoer in de console.

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

Uitbreiden Configuratie

Je weet waarschijnlijk dat ik graag gebruik maak van enums voor het creëren van namespaces. Ik zal u laten zien hoe we de huidige implementatie van de Configuration enum kunnen verbeteren. De eenvoudigste oplossing is het definiëren van een statische, berekende eigenschap, baseURL, van het type URL. U kunt de eigenschap ook definiëren als een statische, constante eigenschap. Dat is een persoonlijke keuze. We gebruiken een switch statement om de basis URL terug te geven voor elke build configuratie.

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")! } }}

Er zijn een aantal details die ik zou willen opmerken. Ten eerste, ik gebruik geen default case. Ik ben expliciet over de waarde die wordt geretourneerd voor elke build configuratie. Dit maakt het makkelijker om problemen op te sporen en het maakt de code leesbaarder en intuïtiever. Ten tweede gebruik ik het uitroepteken om de waarde die wordt geretourneerd door de initializer van de URL struct te forceren. Dit is een van de zeldzame scenario’s waarin ik het uitroepteken gebruik om het uitpakken van een waarde te forceren. Het is handig, maar belangrijker is dat de basis URL nooit gelijk zou moeten zijn aan nil.

Open AppDelegate.swift en print de waarde van de baseURL computed property. Met het schema ingesteld op de Staging, voert u de toepassing en inspecteer de output in de console.

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

Dit patroon heeft een paar voordelen ten opzichte van de eerste oplossing die we hebben geïmplementeerd. We maken gebruik van Swift’s type veiligheid en we hebben niet langer te maken met een optionele waarde. We kunnen ook profiteren van Xcode’s autocompletion. Deze voordelen zijn subtiel, maar je gaat ze na verloop van tijd waarderen.

Laten we de kers op de taart zetten door namespaces aan de mix toe te voegen. Maak een nieuw Swift-bestand en noem het Configuration+DarkSky.swift.

Creating a Swift File

Creating a Swift File

Maak een extensie voor het Configuration-enum en definieer een enum met de naam DarkSky in de extensie. Dark Sky is een weerdienst die ik van tijd tot tijd gebruik.

import Foundationextension Configuration { enum DarkSky { }}

De DarkSky-enum definieert een statische, constante eigenschap, apiKey, van het type String. We schakelen de huidige configuratie in en geven een andere waarde terug voor elke build configuratie. Zoals ik al eerder zei, kunt u de eigenschap ook declareren als een statische, variabele eigenschap. Dat is aan u om te beslissen.

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

Er zijn verschillende voordelen aan deze aanpak. De configuratie voor de Dark Sky API is mooi namespaced. Dit maakt het ook gemakkelijk om de configuratie voor de Dark Sky API in een apart bestand te zetten.

Open AppDelegate.swift en print de API-sleutel voor de Dark Sky weerservice. Met het schema ingesteld op Staging, voert u de toepassing uit en inspecteert u de uitvoer in de console.

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

What’s Next?

Dit patroon voegt gemak, type veiligheid, en veiligheid toe aan uw projecten. Het is eenvoudig toe te passen en, als het eenmaal is geïmplementeerd, is het eenvoudig uit te breiden. Het is een patroon dat ik graag gebruik om verschillende redenen. Ik raad sterk aan om het eens te proberen.