Gestion des configurations de build dans Xcode
Le manuel manquant
pour le développement Swift
Le guide que j’aurais aimé avoir quand j’ai commencé
Rejoignez 20,000+ développeurs qui apprennent le développement Swift
Téléchargez votre exemplaire gratuit
Un tout nouveau projet Xcode définit deux configurations de build, Debug et Release. La plupart des projets définissent une ou plusieurs configurations de build supplémentaires pour diverses raisons. Ce n’est pas nouveau et c’est une bonne pratique d’utiliser les configurations de build pour adapter un build aux besoins spécifiques de l’environnement dans lequel il va être déployé.
Dans cet épisode, je vous montre comment gérer en toute sécurité les données spécifiques à une configuration de build, telles que les clés API, les informations d’identification et d’autres données sensibles. Il existe plusieurs stratégies pour gérer les configurations de construction, mais il y a des différences importantes que vous devez prendre en compte, en particulier dans le contexte de la sécurité.
Ajout d’une configuration
Démarrez Xcode et créez un nouveau projet en choisissant le modèle Single View App dans la section iOS > Application.
Nommez le projet Configurations, réglez le langage sur Swift, et assurez-vous que les cases à cocher en bas sont décochées. Indiquez à Xcode où vous souhaitez stocker le projet et cliquez sur Créer.
Ouvrez le navigateur de projet sur la gauche et cliquez sur le projet en haut. Sélectionnez le projet dans la section Projet pour afficher les détails du projet. Un tout nouveau projet Xcode définit deux configurations de construction, Debug et Release.
Vous utilisez couramment la configuration Debug pendant le développement tandis que la configuration Release est utilisée pour créer des constructions App Store ou TestFlight. Cela n’est probablement pas nouveau pour vous.
De nombreuses applications communiquent avec un backend et c’est une pratique courante d’avoir un environnement de staging et un environnement de production. L’environnement de staging est utilisé pendant le développement. Double-cliquez sur la configuration Debug et renommez-la en Staging.
Ajoutons une troisième configuration pour l’environnement de production. Cliquez sur le bouton + en bas du tableau, choisissez Dupliquer la configuration « Staging » et nommez la configuration Production.
Créons un schéma pour chaque configuration afin de faciliter le passage rapide d’un environnement à l’autre. Sélectionnez le schéma en haut et choisissez Gérer les schémas… dans le menu. Sélectionnez le schéma nommé Configurations et cliquez dessus une fois de plus. Renommez-le en Staging.
Avec le schéma sélectionné, cliquez sur l’icône d’engrenage en bas et choisissez Dupliquer. Nommez le schéma Production. Sélectionnez Exécuter sur la gauche et définissez la configuration de construction sur Production.
C’est tout. Nous avons maintenant une configuration de construction pour la mise en scène et la production. Les schémas permettent de passer rapidement et facilement d’une configuration de build à l’autre.
Paramètres de build définis par l’utilisateur
Comme je l’ai mentionné précédemment, il existe plusieurs solutions pour gérer les données qui sont spécifiques à une configuration de build particulière. Dans cet épisode, je montre une solution que j’utilise dans tout projet qui présente une certaine complexité. Avant d’exposer la solution que j’ai en tête, j’aimerais vous montrer une autre solution qui est souvent utilisée par les développeurs.
Choisissez le projet dans le navigateur de projet à gauche. Sélectionnez la cible Configurations dans la section Cibles et cliquez sur l’onglet Paramètres de construction en haut.
L’onglet Paramètres de construction affiche les paramètres de construction pour la cible Configurations. Il est possible d’étendre cette liste avec des paramètres de construction que vous définissez. Cliquez sur le bouton + en haut et choisissez Ajouter un paramètre défini par l’utilisateur.
Nommez le paramètre défini par l’utilisateur BASE_URL. Définir une URL de base est courant pour les applications qui interagissent avec un backend.
Quel est l’avantage de définir un paramètre défini par l’utilisateur ? Le paramètre défini par l’utilisateur nous permet de définir une valeur pour BASE_URL pour chaque configuration de construction. Cliquez sur le triangle à gauche du paramètre défini par l’utilisateur pour afficher la liste des configurations de build.
Définissez la valeur pour Production et Release à https://cocoacasts.com et la valeur pour Staging à https://staging.cocoacasts.com.
Comme son nom l’indique, un paramètre de build est disponible pendant le processus de build. Votre code ne peut pas accéder directement aux paramètres de build que vous définissez. C’est une idée fausse très répandue. Il y a pourtant une solution. Ouvrez Info.plist et ajoutez une nouvelle paire clé/valeur. Définissez la clé sur BASE_URL et la valeur sur $(BASE_URL)
.
Quel est l’intérêt d’ajouter une paire clé/valeur à l’Info.plist du projet ? La valeur de la paire clé/valeur est mise à jour au moment de la construction. Xcode inspecte les paramètres de construction pour la configuration de construction actuelle et définit la valeur de la clé BASE_URL dans l’Info.plist du projet.
Essayons-le. Ouvrez AppDelegate.swift et naviguez jusqu’à la méthode application(_:didFinishLaunchingWithOptions:)
. Nous devons accéder au contenu du fichier Info.plist. Cela est possible grâce à la propriété infoDictionary
de la classe Bundle
. Le bundle qui nous intéresse est le bundle principal. Comme son nom l’indique, la propriété infoDictionary
est un dictionnaire de paires clé/valeur et nous accédons à la valeur de la clé BASE_URL
.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Bundle.main.infoDictionary?) return true}
Sélectionnez le schéma de production en haut et exécutez l’application dans le simulateur. Inspectez la sortie dans la console.
Optional(https://staging.cocoacasts.com)
Sélectionnez le schéma Staging en haut et exécutez l’application dans le simulateur. Inspectez la sortie dans la console. C’est plutôt bien. Pas vrai ?
Optional(https://staging.cocoacasts.com)
Un mot sur la sécurité
La plupart des développeurs trouvent cette solution très pratique. Et elle l’est. Il y a cependant un problème. Il est facile d’extraire le fichier Info.plist des applications téléchargées sur l’App Store. Que se passe-t-il si vous stockez des clés d’API, des informations d’identification ou d’autres informations sensibles dans le fichier Info.plist ? Cela introduit un risque de sécurité important que nous pouvons et devons éviter.
J’aimerais vous montrer une solution simple qui offre la même flexibilité, la même sécurité de type et une sécurité améliorée. Créez un nouveau fichier Swift et nommez-le Configuration.swift.
Définissez un enum avec le nom Configuration et une valeur brute de type String
. Nous définissons un enum pour chaque configuration de construction, staging
, production
et release
.
import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release}
Avant de continuer avec l’implémentation de l’enum Configuration, nous devons mettre à jour le fichier Info.plist. Ouvrez Info.plist et ajoutez une paire clé/valeur. La clé est Configuration et la valeur est $(CONFIGURATION)
. La valeur de CONFIGURATION est automatiquement définie pour nous. Nous n’avons pas besoin de nous en préoccuper. Comme son nom l’indique, la valeur est égale au nom de la configuration du build avec laquelle le build est créé.
Revisitez Configuration.swift. Nous voulons un accès facile à la configuration du build, la valeur stockée dans le fichier Info.plist du projet. Définissez une propriété statique et constante, current
, de type Configuration
. Nous accédons à la valeur stockée dans le fichier Info.plist pour la clé Configuration et la transformons en une instance String
. Nous lançons une erreur fatale si cela échoue car cela ne devrait jamais arriver.
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") } }()}
Nous utilisons la valeur du fichier Info.plist pour créer une instance Configuration
. Nous lançons une autre erreur fatale si l’initialisation échoue. Remarquez que nous mettons en minuscules la valeur stockée dans le fichier Info.plist. L’instance Configuration
est renvoyée par la fermeture. N’oubliez pas d’ajouter une paire de parenthèses à la fermeture.
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 }()}
Essayons-le. Ouvrez AppDelegate.swift et imprimez la configuration actuelle. Sélectionnez le schéma Staging, exécutez l’application et inspectez la sortie dans la console.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.current) return true}
staging
Extension de la configuration
Vous savez probablement que j’aime exploiter les enums pour créer des espaces de noms. Laissez-moi vous montrer comment nous pouvons améliorer l’implémentation actuelle de l’enum Configuration
. La solution la plus simple consiste à définir une propriété statique et calculée, baseURL
, de type URL
. Vous pouvez également définir la propriété comme une propriété statique et constante. C’est un choix personnel. Nous utilisons une déclaration switch
pour renvoyer l’URL de base pour chaque configuration de construction.
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")! } }}
Il y a plusieurs détails que je voudrais souligner. Premièrement, je n’utilise pas de cas default
. Je suis explicite sur la valeur qui est retournée pour chaque configuration de construction. Cela permet de repérer plus facilement les problèmes et cela rend le code plus lisible et plus intuitif. Deuxièmement, j’utilise le point d’exclamation pour forcer le déballage de la valeur renvoyée par l’initialisateur de la structure URL
. C’est l’un des rares scénarios dans lesquels j’utilise le point d’exclamation pour forcer le déballage d’une valeur. C’est pratique, mais, plus important encore, l’URL de base ne devrait jamais être égale à nil
.
Ouvrir AppDelegate.swift et imprimer la valeur de la propriété calculée baseURL
. Avec le schéma défini sur le Staging, exécutez l’application et inspectez la sortie dans la console.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.baseURL) return true}
https://staging.cocoacasts.com
Ce modèle présente quelques avantages par rapport à la première solution que nous avons mise en œuvre. Nous profitons de la sécurité des types de Swift et nous n’avons plus affaire à une valeur optionnelle. Nous pouvons également bénéficier de l’autocomplétion de Xcode. Ces avantages sont subtils, mais on finit par les apprécier avec le temps.
Mettons la cerise sur le gâteau en ajoutant les espaces de noms au mélange. Créez un nouveau fichier Swift et nommez-le Configuration+DarkSky.swift.
Créer une extension pour l’enum Configuration et définir un enum avec le nom DarkSky
dans l’extension. Dark Sky est un service météo que j’utilise de temps en temps.
import Foundationextension Configuration { enum DarkSky { }}
L’enum DarkSky
définit une propriété statique et constante, apiKey
, de type String
. On bascule sur la configuration actuelle et on retourne une valeur différente pour chaque configuration de construction. Comme je l’ai mentionné précédemment, vous pouvez également déclarer la propriété comme une propriété statique, variable. C’est à vous de décider.
import Foundationextension Configuration { enum DarkSky { static let apiKey: String = { switch Configuration.current { case .staging: return "123" case .production: return "456" case .release: return "789" } }() }}
Il y a plusieurs avantages à cette approche. La configuration de l’API Dark Sky est joliment espacée par des noms. Cela permet également de mettre facilement la configuration pour l’API Dark Sky dans un fichier séparé.
Ouvrir AppDelegate.swift et imprimer la clé API pour le service météorologique Dark Sky. Avec le schéma défini sur Staging, exécutez l’application et inspectez la sortie dans la console.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.DarkSky.apiKey) return true}
123
Quoi d’autre ?
Ce pattern ajoute de la commodité, de la sécurité de type et de la sécurité à vos projets. Il est facile à adopter et, une fois qu’il est implémenté, il est simple à étendre. C’est un pattern que j’aime utiliser pour toute une série de raisons. Je vous recommande vivement de l’essayer.