Une bibliothèque de gestion des dépendances inspirée de «l'environnement» de Swiftui.
Cette bibliothèque a été motivée et conçue au cours de nombreux épisodes sur Point-Free, une série de vidéos explorant la programmation fonctionnelle et le langage Swift, hébergé par Brandon Williams et Stephen Celis.
Les dépendances sont les types et les fonctions de votre application qui doivent interagir avec les systèmes extérieurs que vous ne contrôlez pas. Des exemples classiques de cela sont des clients d'API qui font des demandes de réseau aux serveurs, mais aussi des choses apparemment inoffensives telles que UUID
et les initialiseurs Date
, l'accès au fichier, les défaillances des utilisateurs et même les horloges et les minuteries, peuvent tous être considérés comme des dépendances.
Vous pouvez aller très loin dans le développement des applications sans jamais penser à la gestion des dépendances (ou, comme certains aiment l'appeler, "injection de dépendance"), mais finalement les dépendances incontrôlées peuvent causer de nombreux problèmes dans votre base de code et votre cycle de développement:
Les dépendances non contrôlées rendent difficile l'écriture de tests rapidement et déterministes parce que vous êtes sensible aux caprices du monde extérieur, tels que les systèmes de fichiers, la connectivité réseau, la vitesse Internet, la disponibilité du serveur, etc.
De nombreuses dépendances ne fonctionnent pas bien dans les aperçus Swiftui , tels que les gestionnaires de localisation et les reconnaissances de la parole, et certains ne fonctionnent même pas dans les simulateurs , tels que les gestionnaires de mouvement, etc. Cela vous empêche de pouvoir itérer facilement sur la conception des fonctionnalités si vous utilisez ces frameworks.
Les dépendances qui interagissent avec les bibliothèques non punaises (comme les bibliothèques de socket Web, les bibliothèques de réseaux, etc.) ont tendance à être lourds et à prendre beaucoup de temps à compiler . Cela peut ralentir votre cycle de développement.
Pour ces raisons, et bien plus encore, il est fortement encouragé pour vous de prendre le contrôle de vos dépendances plutôt que de les laisser vous contrôler.
Mais le contrôle d'une dépendance n'est que le début. Une fois que vous avez contrôlé vos dépendances, vous êtes confronté à un ensemble de nouveaux problèmes:
Comment pouvez-vous propager les dépendances tout au long de votre application d'une manière plus ergonomique que de les passer explicitement partout, mais plus sûres que d'avoir une dépendance globale?
Comment pouvez-vous remplacer les dépendances pour une seule partie de votre application? Cela peut être pratique pour remplacer les dépendances pour les tests et les aperçus Swiftui, ainsi que des flux d'utilisateurs spécifiques tels que les expériences d'intégration.
Comment pouvez-vous être sûr de dépasser toutes les dépendances qu'une fonctionnalité utilise dans les tests? Il serait incorrect qu'un test se moque de certaines dépendances, mais laisse d'autres comme interagissant avec le monde extérieur.
Cette bibliothèque aborde tous les points ci-dessus, et bien plus encore.
La bibliothèque vous permet d'enregistrer vos propres dépendances, mais elle est également livrée avec de nombreuses dépendances contrôlables hors de la boîte (voir DependencyValues
pour une liste complète), et il y a de fortes chances que vous puissiez en utiliser immédiatement une. Si vous utilisez Date()
, UUID()
, Task.sleep
ou combinez les planificateurs directement dans la logique de votre fonctionnalité, vous pouvez déjà commencer à utiliser cette bibliothèque.
@ Observable
final class FeatureModel {
var items : [ Item ] = [ ]
@ ObservationIgnored
@ Dependency ( . continuousClock ) var clock // Controllable way to sleep a task
@ ObservationIgnored
@ Dependency ( . date . now ) var now // Controllable way to ask for current date
@ ObservationIgnored
@ Dependency ( . mainQueue ) var mainQueue // Controllable scheduling on main queue
@ ObservationIgnored
@ Dependency ( . uuid ) var uuid // Controllable UUID creation
// ...
}
Une fois vos dépendances déclarées, plutôt que de contacter la Date()
, UUID()
, etc., directement, vous pouvez utiliser la dépendance définie sur le modèle de votre fonctionnalité:
@ Observable
final class FeatureModel {
// ...
func addButtonTapped ( ) async throws {
try await clock . sleep ( for : . seconds ( 1 ) ) // ? Don't use 'Task.sleep'
items . append (
Item (
id : uuid ( ) , // ? Don't use 'UUID()'
name : " " ,
createdAt : now // ? Don't use 'Date()'
)
)
}
}
C'est tout ce qu'il faut pour commencer à utiliser des dépendances contrôlables dans vos fonctionnalités. Avec ce petit travail initial effectué, vous pouvez commencer à profiter des pouvoirs de la bibliothèque.
Par exemple, vous pouvez facilement contrôler ces dépendances dans les tests. Si vous souhaitez tester la logique à l'intérieur de la méthode addButtonTapped
, vous pouvez utiliser la fonction withDependencies
pour remplacer toutes les dépendances pour la portée d'un seul test. C'est aussi simple que 1-2-3:
@ Test
func add ( ) async throws {
let model = withDependencies {
// 1️⃣ Override any dependencies that your feature uses.
$0 . clock = . immediate
$0 . date . now = Date ( timeIntervalSinceReferenceDate : 1234567890 )
$0 . uuid = . incrementing
} operation : {
// 2️⃣ Construct the feature's model
FeatureModel ( )
}
// 3️⃣ The model now executes in a controlled environment of dependencies,
// and so we can make assertions against its behavior.
try await model . addButtonTapped ( )
#expect (
model . items == [
Item (
id : UUID ( uuidString : " 00000000-0000-0000-0000-000000000000 " ) ! ,
name : " " ,
createdAt : Date ( timeIntervalSinceReferenceDate : 1234567890 )
)
]
)
}
Ici, nous avons contrôlé la dépendance date
pour toujours renvoyer la même date, et nous avons contrôlé la dépendance uuid
pour retourner un UUID à incrémentation automatique à chaque fois qu'il est invoqué, et nous avons même contrôlé la dépendance clock
en utilisant un ImmediateClock
pour écraser tout le temps en un seul seul instantané. Si nous ne contrôlions pas ces dépendances, ce test serait très difficile à écrire car il n'y a aucun moyen de prédire avec précision ce qui sera retourné par Date()
et UUID()
, et nous devons attendre le temps du monde réel pour passer, Rendre le test lent.
Mais, les dépendances contrôlables ne sont pas seulement utiles pour les tests. Ils peuvent également être utilisés dans les aperçus Xcode. Supposons que la fonctionnalité ci-dessus utilise une horloge pour dormir pendant un temps avant que quelque chose ne se produise dans la vue. Si vous ne voulez pas attendre littéralement le temps de passer afin de voir comment la vue change, vous pouvez remplacer la dépendance de l'horloge pour être une horloge "immédiate" en utilisant le trait d'aperçu .dependencies
#Preview (
traits : . dependencies {
$0 . continuousClock = . immediate
}
) {
// All access of '@Dependency(.continuousClock)' in this preview will
// use an immediate clock.
FeatureView ( model : FeatureModel ( ) )
}
Cela fera en sorte que l'aperçu utilise une horloge immédiate lors de l'exécution, mais lors de l'exécution dans un simulateur ou sur l'appareil, il utilisera toujours un ContinuousClock
en direct. Cela permet de remplacer les dépendances uniquement pour les aperçus sans affecter la façon dont votre application se déroulera en production.
C'est les bases pour commencer à utiliser la bibliothèque, mais vous pouvez encore faire beaucoup plus. Vous pouvez en savoir plus en profondeur sur la bibliothèque en explorant la documentation et les articles:
Démarrage rapide (Identique à l'information ci-dessus) : Apprenez les bases du démarrage avec la bibliothèque avant de plonger profondément dans toutes ses fonctionnalités.
Quelles sont les dépendances? : Apprenez ce que sont les dépendances, comment elles compliquent votre code et pourquoi vous souhaitez les contrôler.
Utilisation des dépendances : Apprenez à utiliser les dépendances enregistrées auprès de la bibliothèque.
Enregistrement des dépendances : apprenez à enregistrer vos propres dépendances auprès de la bibliothèque afin qu'elles soient immédiatement disponibles à partir de n'importe quelle partie de votre base de code.
Dépendance en direct, prévisualisation et test : Apprenez à fournir des implémentations différentes de vos dépendances à utiliser dans l'application en direct, ainsi qu'aux aperçus Xcode, et même dans les tests.
Test : L'une des principales raisons de contrôler les dépendances est de permettre des tests plus faciles. Apprenez quelques conseils et astuces pour rédiger de meilleurs tests avec la bibliothèque.
Concevoir des dépendances : apprenez des techniques sur la conception de vos dépendances afin qu'elles soient les plus flexibles pour injecter des fonctionnalités et remplacer les tests.
Dépendance à la remplacement : découvrez comment les dépendances peuvent être modifiées au moment de l'exécution afin que certaines parties de votre application puissent utiliser différentes dépendances.
Distimes de dépendance : découvrez les durées de vie des dépendances, comment prolonger la durée de vie d'une dépendance et comment les dépendances sont héritées.
Systèmes de points d'entrée unique : découvrez les systèmes de «point d'entrée unique» et pourquoi ils sont les mieux adaptés à cette bibliothèque de dépendances, bien qu'il soit possible d'utiliser la bibliothèque avec des systèmes de points d'entrée non tootants.
Nous avons reconstruit l'application de démonstration de scrumdinger d'Apple en utilisant les meilleures pratiques modernes pour le développement de Swiftui, y compris l'utilisation de cette bibliothèque pour contrôler les dépendances sur l'accès au système de fichiers, les minuteries et les API de reconnaissance vocale. Cette démo peut être trouvée ici.
La dernière documentation des API des dépendances est disponible ici.
Vous pouvez ajouter des dépendances à un projet Xcode en l'ajoutant à votre projet en tant que package.
https://github.com/pointfreeco/swift-dependcesnces
Si vous souhaitez utiliser des dépendances dans un projet SWIFTPM, c'est aussi simple que de l'ajouter à votre Package.swift
:
dependencies: [
. package ( url : " https://github.com/pointfreeco/swift-dependencies " , from : " 1.0.0 " )
]
Puis ajouter le produit à toute cible qui a besoin d'accès à la bibliothèque:
. product ( name : " Dependencies " , package : " swift-dependencies " ) ,
Si vous souhaitez discuter de cette bibliothèque ou si vous avez une question sur la façon de l'utiliser pour résoudre un problème particulier, il existe un certain nombre d'endroits à discuter avec les autres passionnés sans point:
Cette bibliothèque contrôle un certain nombre de dépendances hors de la boîte, mais est également ouverte à l'extension. Les projets suivants se construisent tous au sommet des dépendances:
Il existe de nombreuses autres bibliothèques d'injection de dépendance dans la communauté Swift. Chacun a son propre ensemble de priorités et de compromis qui diffèrent des dépendances. Voici quelques exemples bien connus:
Cette bibliothèque est publiée sous la licence MIT. Voir la licence pour plus de détails.