Articles

Verwalten von Build-Konfigurationen in Xcode

Ressourcen

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Entwickler, die die Swift-Entwicklung erlernen

Laden Sie Ihr kostenloses Exemplar herunter

Ein brandneues Xcode-Projekt definiert zwei Build-Konfigurationen, Debug und Release. Die meisten Projekte definieren aus verschiedenen Gründen eine oder mehrere zusätzliche Build-Konfigurationen. Das ist nicht neu, und es ist eine gute Praxis, Build-Konfigurationen zu verwenden, um einen Build an die spezifischen Anforderungen der Umgebung anzupassen, in der er bereitgestellt werden soll.

In dieser Folge zeige ich Ihnen, wie Sie Daten, die für eine Build-Konfiguration spezifisch sind, wie API-Schlüssel, Anmeldeinformationen und andere sensible Daten sicher verwalten können. Es gibt mehrere Strategien, um Build-Konfigurationen zu verwalten, aber es gibt wichtige Unterschiede, die Sie berücksichtigen müssen, insbesondere im Zusammenhang mit der Sicherheit.

Hinzufügen einer Konfiguration

Starten Sie Xcode und erstellen Sie ein neues Projekt, indem Sie die Single View App-Vorlage aus dem iOS >-Anwendungsabschnitt auswählen.

Setting Up the Project

Benennen Sie das Projekt Configurations, stellen Sie die Sprache auf Swift ein und stellen Sie sicher, dass die Kontrollkästchen am unteren Rand nicht markiert sind. Teilen Sie Xcode mit, wo Sie das Projekt speichern möchten und klicken Sie auf Erstellen.

Setting Up the Project

Öffnen Sie den Projektnavigator auf der linken Seite und klicken Sie oben auf das Projekt. Wählen Sie das Projekt im Abschnitt Projekt aus, um die Projektdetails anzuzeigen. Ein brandneues Xcode-Projekt definiert zwei Build-Konfigurationen, Debug und Release.

Build Configurations in Xcode

Die Debug-Konfiguration verwenden Sie in der Regel während der Entwicklung, während die Release-Konfiguration für die Erstellung von App Store- oder TestFlight-Builds verwendet wird. Das ist wahrscheinlich nicht neu für Sie.

Viele Anwendungen kommunizieren mit einem Backend, und es ist eine gängige Praxis, eine Staging- und eine Produktionsumgebung zu haben. Die Staging-Umgebung wird während der Entwicklung verwendet. Doppelklicken Sie auf die Konfiguration Debug und benennen Sie sie in Staging um.

Build Configurations in Xcode

Lassen Sie uns eine dritte Konfiguration für die Produktionsumgebung hinzufügen. Klicken Sie auf die Schaltfläche „+“ unten in der Tabelle, wählen Sie „Staging“-Konfiguration duplizieren und benennen Sie die Konfiguration Production.

Duplicating a Build Configuration

Duplicating a Build Configuration

Lassen Sie uns für jede Konfiguration ein Schema erstellen, damit Sie schnell zwischen den Umgebungen wechseln können. Wählen Sie das Schema oben aus und wählen Sie Schemata verwalten… aus dem Menü. Wählen Sie das Schema mit dem Namen Configurations und klicken Sie noch einmal darauf. Benennen Sie es in Staging um.

Update Schemes

Klicken Sie bei ausgewähltem Schema auf das Zahnradsymbol am unteren Rand und wählen Sie Duplizieren. Nennen Sie das Schema Produktion. Wählen Sie Ausführen auf der linken Seite und setzen Sie Build-Konfiguration auf Produktion.

Update Schemes

Das war’s. Wir haben jetzt eine Build-Konfiguration für Staging und Produktion. Die Schemata machen es schnell und einfach, zwischen Build-Konfigurationen zu wechseln.

Benutzerdefinierte Build-Einstellungen

Wie ich bereits erwähnt habe, gibt es mehrere Lösungen, um Daten zu verwalten, die für eine bestimmte Build-Konfiguration spezifisch sind. In dieser Folge zeige ich eine Lösung, die ich in jedem Projekt verwende, das eine gewisse Komplexität aufweist. Bevor ich die Lösung vorstelle, die mir vorschwebt, möchte ich Ihnen eine andere Lösung zeigen, die häufig von Entwicklern verwendet wird.

Wählen Sie das Projekt im Projektnavigator auf der linken Seite. Wählen Sie im Abschnitt Ziele das Ziel Konfigurationen aus und klicken Sie oben auf die Registerkarte Build-Einstellungen.

Target Build Settings

Die Registerkarte Build-Einstellungen zeigt die Build-Einstellungen für das Ziel Konfigurationen an. Es ist möglich, diese Liste mit Build-Einstellungen zu erweitern, die Sie definieren. Klicken Sie oben auf die Schaltfläche + und wählen Sie Benutzerdefinierte Einstellung hinzufügen.

Adding a User-Defined Setting

Benennen Sie die benutzerdefinierte Einstellung BASE_URL. Die Definition einer Basis-URL ist üblich für Anwendungen, die mit einem Backend interagieren.

Adding a User-Defined Setting

Was ist der Vorteil der Definition einer benutzerdefinierten Einstellung? Die benutzerdefinierte Einstellung ermöglicht es uns, einen Wert für BASE_URL für jede Build-Konfiguration festzulegen. Klicken Sie auf das Dreieck links neben der benutzerdefinierten Einstellung, um die Liste der Build-Konfigurationen anzuzeigen.

Adding a User-Defined Setting

Setzen Sie den Wert für Production und Release auf https://cocoacasts.com und den Wert für Staging auf https://staging.cocoacasts.com.

Adding a User-Defined Setting

Wie der Name schon sagt, ist eine Build-Einstellung während des Build-Prozesses verfügbar. Ihr Code kann nicht direkt auf die von Ihnen definierten Build-Einstellungen zugreifen. Das ist ein weit verbreiteter Irrglaube. Es gibt jedoch eine Lösung. Öffnen Sie Info.plist und fügen Sie ein neues Schlüssel/Wert-Paar hinzu. Setzen Sie den Schlüssel auf BASE_URL und den Wert auf $(BASE_URL).

Updating Info.plist

Was bringt das Hinzufügen eines Schlüssel/Wertpaares zur Info.plist des Projekts? Der Wert des Schlüssel/Wertpaares wird zum Zeitpunkt der Erstellung aktualisiert. Xcode prüft die Build-Einstellungen für die aktuelle Build-Konfiguration und legt den Wert des Schlüssels BASE_URL in der Info.plist des Projekts fest.

Lassen Sie es uns ausprobieren. Öffnen Sie AppDelegate.swift und navigieren Sie zu der Methode application(_:didFinishLaunchingWithOptions:). Wir müssen auf den Inhalt der Datei Info.plist zugreifen. Dies ist über die infoDictionary-Eigenschaft der Bundle-Klasse möglich. Das Bundle, an dem wir interessiert sind, ist das Main-Bundle. Wie der Name schon sagt, ist die infoDictionary-Eigenschaft ein Wörterbuch mit Schlüssel/Wert-Paaren, und wir greifen auf den Wert für den Schlüssel BASE_URL zu.

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

Wählen Sie oben das Produktionsschema aus und führen Sie die Anwendung im Simulator aus. Überprüfen Sie die Ausgabe in der Konsole.

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

Wählen Sie oben das Staging-Schema und führen Sie die Anwendung im Simulator aus. Sehen Sie sich die Ausgabe in der Konsole an. Das sieht doch ganz gut aus. Richtig?

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

Ein Wort zur Sicherheit

Die meisten Entwickler finden diese Lösung sehr bequem. Und das ist sie auch. Allerdings gibt es ein Problem. Es ist einfach, die Datei „Info.plist“ aus Anwendungen zu extrahieren, die aus dem App Store heruntergeladen wurden. Was passiert, wenn Sie API-Schlüssel, Anmeldeinformationen oder andere sensible Informationen in der Info.plist-Datei speichern? Dies stellt ein erhebliches Sicherheitsrisiko dar, das wir vermeiden können und sollten.

Ich möchte Ihnen eine einfache Lösung zeigen, die dieselbe Flexibilität, Typsicherheit und verbesserte Sicherheit bietet. Erstellen Sie eine neue Swift-Datei und nennen Sie sie Configuration.swift.

Creating a Swift File

Creating a Swift File

Definieren Sie ein Enum mit dem Namen Configuration und einem Rohwert vom Typ String. Wir definieren einen Fall für jede Build-Konfiguration, staging, production und release.

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

Bevor wir mit der Implementierung der Configuration-Enum fortfahren, müssen wir die Datei Info.plist aktualisieren. Öffnen Sie Info.plist und fügen Sie ein Schlüssel/Wert-Paar hinzu. Der Schlüssel ist Configuration und der Wert ist $(CONFIGURATION). Der Wert von CONFIGURATION wird automatisch für uns festgelegt. Wir brauchen uns nicht darum zu kümmern. Wie der Name schon sagt, ist der Wert gleich dem Namen der Build-Konfiguration, mit der der Build erstellt wird.

Update Info.plist

Besuchen Sie Configuration.swift. Wir wollen einfachen Zugriff auf die Konfiguration des Builds, den Wert, der in der Datei Info.plist des Projekts gespeichert ist. Definieren Sie eine statische, konstante Eigenschaft, current, vom Typ Configuration. Wir greifen auf den Wert zu, der in der Datei Info.plist für den Schlüssel Configuration gespeichert ist, und wandeln ihn in eine String-Instanz um. Wir geben einen schwerwiegenden Fehler aus, wenn dies fehlschlägt, da dies nie passieren sollte.

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

Wir verwenden den Wert aus der Datei Info.plist, um eine Configuration-Instanz zu erstellen. Wir geben einen weiteren fatalen Fehler aus, wenn die Initialisierung fehlschlägt. Beachten Sie, dass wir den in der Datei Info.plist gespeicherten Wert klein schreiben. Die Instanz Configuration wird von der Schließung zurückgegeben. Vergessen Sie nicht, ein Klammerpaar an die Schließung anzuhängen.

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

Lassen Sie uns das ausprobieren. Öffnen Sie AppDelegate.swift und geben Sie die aktuelle Konfiguration aus. Wählen Sie das Staging-Schema, führen Sie die Anwendung aus und überprüfen Sie die Ausgabe in der Konsole.

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

Erweiterung der Konfiguration

Sie wissen wahrscheinlich, dass ich gerne Enums für die Erstellung von Namespaces verwende. Ich möchte Ihnen zeigen, wie wir die aktuelle Implementierung der Configuration-Enum verbessern können. Die einfachste Lösung besteht darin, eine statische, berechnete Eigenschaft baseURL vom Typ URL zu definieren. Sie können die Eigenschaft auch als statische, konstante Eigenschaft definieren. Das ist eine persönliche Entscheidung. Wir verwenden eine switch-Anweisung, um die Basis-URL für jede Build-Konfiguration zurückzugeben.

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

Es gibt einige Details, auf die ich hinweisen möchte. Erstens verwende ich keinen default-Fall. Ich gebe den Wert, der für jede Build-Konfiguration zurückgegeben wird, explizit an. Dadurch lassen sich Probleme leichter erkennen, und der Code wird lesbarer und intuitiver. Zweitens verwende ich das Ausrufezeichen, um den vom Initialisierer der URL-Struktur zurückgegebenen Wert zu entpacken. Dies ist eines der seltenen Szenarien, in denen ich das Ausrufezeichen verwende, um das Auspacken eines Wertes zu erzwingen. Es ist praktisch, aber noch wichtiger ist, dass die Basis-URL niemals gleich nil.

Öffnen Sie AppDelegate.swift und geben Sie den Wert der baseURL berechneten Eigenschaft aus. Wenn das Schema auf Staging eingestellt ist, führen Sie die Anwendung aus und überprüfen Sie die Ausgabe in der Konsole.

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

Dieses Muster hat einige Vorteile gegenüber der ersten von uns implementierten Lösung. Wir profitieren von der Typsicherheit von Swift und haben es nicht mehr mit einem optionalen Wert zu tun. Wir können auch von der Autovervollständigung von Xcode profitieren. Diese Vorteile sind subtil, aber man lernt sie mit der Zeit zu schätzen.

Lassen Sie uns das Sahnehäubchen auf den Kuchen setzen, indem wir Namespaces zur Mischung hinzufügen. Erstellen Sie eine neue Swift-Datei und nennen Sie sie Configuration+DarkSky.swift.

Creating a Swift File

Creating a Swift File

Erstellen Sie eine Erweiterung für das Configuration-Enum und definieren Sie ein Enum mit Namen DarkSky in der Erweiterung. Dark Sky ist ein Wetterdienst, den ich von Zeit zu Zeit benutze.

import Foundationextension Configuration { enum DarkSky { }}

Das Enum DarkSky definiert eine statische, konstante Eigenschaft, apiKey, vom Typ String. Wir schalten die aktuelle Konfiguration ein und geben für jede Build-Konfiguration einen anderen Wert zurück. Wie ich bereits erwähnt habe, können Sie die Eigenschaft auch als statische, variable Eigenschaft deklarieren. Das bleibt Ihnen überlassen.

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

Dieser Ansatz hat mehrere Vorteile. Die Konfiguration für die Dark Sky-API ist sehr gut mit einem Namensraum versehen. Das macht es auch einfach, die Konfiguration für die Dark Sky API in eine separate Datei zu legen.

Öffnen Sie AppDelegate.swift und geben Sie den API-Schlüssel für den Dark Sky Wetterdienst aus. Führen Sie die Anwendung mit dem auf Staging eingestellten Schema aus und überprüfen Sie die Ausgabe in der Konsole.

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

Was kommt als Nächstes?

Dieses Muster fügt Ihren Projekten Komfort, Typsicherheit und Sicherheit hinzu. Es ist einfach zu übernehmen und, sobald es implementiert ist, ist es leicht zu erweitern. Ich verwende dieses Muster aus einer Reihe von Gründen gerne. Ich empfehle dringend, es einmal auszuprobieren.