Una biblioteca de gestión de dependencias inspirada en el "entorno" de Swiftui.
Esta biblioteca fue motivada y diseñada en el transcurso de muchos episodios en Point-Free, una serie de videos que explora la programación funcional y el lenguaje rápido, organizado por Brandon Williams y Stephen Celis.
Las dependencias son los tipos y funciones en su aplicación que necesitan interactuar con sistemas externos que no controle. Los ejemplos clásicos de esto son clientes API que realizan solicitudes de red a los servidores, pero también cosas aparentemente inocuas como UUID
y inicializadores Date
, acceso a archivos, valores predeterminados de los usuarios e incluso relojes y temporizadores, pueden considerarse como dependencias.
Puede llegar realmente lejos en el desarrollo de aplicaciones sin pensar en la gestión de la dependencia (o, como a algunos les gusta llamarlo, "inyección de dependencia"), pero finalmente las dependencias no controladas pueden causar muchos problemas en su base de código y ciclo de desarrollo:
Las dependencias no controladas hacen que sea difícil escribir pruebas deterministas rápidas porque es susceptible a los caprichos del mundo exterior, como sistemas de archivos, conectividad de red, velocidad de Internet, tiempo de actividad del servidor y más.
Muchas dependencias no funcionan bien en las vistas previas de Swiftui , como los gerentes de ubicación y los reconocedores de voz, y algunas no funcionan ni siquiera en simuladores , como gerentes de movimiento, y más. Esto evita que pueda iterar fácilmente en el diseño de características si utiliza esos marcos.
Las dependencias que interactúan con un tercero, las bibliotecas sin applejo (como Firebase, bibliotecas de sockets web, bibliotecas de redes, etc.) tienden a ser de peso pesado y tardan mucho en compilar . Esto puede ralentizar su ciclo de desarrollo.
Por estas razones, y mucho más, es muy animado que tome el control de sus dependencias en lugar de dejar que lo controlen.
Pero, controlar una dependencia es solo el comienzo. Una vez que haya controlado sus dependencias, se enfrenta a un conjunto completo de nuevos problemas:
¿Cómo puede propagar dependencias a lo largo de toda su aplicación de una manera más ergonómica que pasarlas explícitamente por todas partes, pero más segura que tener una dependencia global?
¿Cómo puede anular las dependencias para una sola parte de su aplicación? Esto puede ser útil para anular las dependencias para las pruebas y las vistas previas de Swiftui, así como los flujos de usuarios específicos, como las experiencias de incorporación.
¿Cómo puede asegurarse de anular todas las dependencias que usa una función en las pruebas? Sería incorrecto para una prueba burlarse de algunas dependencias, pero dejaría que otras interactúen con el mundo exterior.
Esta biblioteca aborda todos los puntos anteriores, y mucho, mucho más.
La biblioteca le permite registrar sus propias dependencias, pero también viene con muchas dependencias controlables de la caja (consulte DependencyValues
para una lista completa), y hay una buena posibilidad de que pueda usar de inmediato una. Si está utilizando Date()
, UUID()
, Task.sleep
o combina programadores directamente en la lógica de su función, ya puede comenzar a usar esta biblioteca.
@ 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
// ...
}
Una vez que se declaran sus dependencias, en lugar de comunicarse con la Date()
, UUID()
, etc., directamente, puede usar la dependencia que se define en el modelo de su característica:
@ 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()'
)
)
}
}
Eso es todo lo que se necesita para comenzar a usar dependencias controlables en sus características. Con ese poco de trabajo por adelantado realizado, puede comenzar a aprovechar los poderes de la biblioteca.
Por ejemplo, puede controlar fácilmente estas dependencias en las pruebas. Si desea probar la lógica dentro del método addButtonTapped
, puede usar la función withDependencies
para anular cualquier dependencia para el alcance de una sola prueba. Es tan fácil como 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 )
)
]
)
}
Aquí controlamos la dependencia date
para devolver siempre la misma fecha, y controlamos la dependencia uuid
para devolver un UUID de incremento automático cada vez que se invoca, e incluso controlamos la dependencia clock
utilizando un ImmediateClock
para aplastar todo el tiempo en un solo tiempo instante. Si no controlamos estas dependencias, esta prueba sería muy difícil de escribir, ya que no hay forma de predecir con precisión lo que se devolverá por Date()
y UUID()
, y tendríamos que esperar a que pase el tiempo del mundo real, Hacer la prueba lenta.
Pero, las dependencias controlables no solo son útiles para las pruebas. También se pueden usar en las vistas previas de Xcode. Supongamos que la característica anterior utiliza un reloj para dormir durante una cantidad de tiempo antes de que algo suceda en la vista. Si no desea esperar literalmente el tiempo para ver cómo cambia la vista, puede anular la dependencia del reloj para que sea un reloj "inmediato" utilizando el rasgo de vista previa .dependencies
:
#Preview (
traits : . dependencies {
$0 . continuousClock = . immediate
}
) {
// All access of '@Dependency(.continuousClock)' in this preview will
// use an immediate clock.
FeatureView ( model : FeatureModel ( ) )
}
Esto hará que la vista previa use un reloj inmediato cuando se ejecute, pero cuando se ejecuta en un simulador o en el dispositivo, todavía usará un Clock Live ContinuousClock
. Esto permite anular las dependencias solo para avances sin afectar cómo se ejecutará su aplicación en producción.
Ese es lo básico para comenzar a usar la biblioteca, pero todavía hay mucho más que puede hacer. Puede obtener más información sobre la biblioteca explorando la documentación y los artículos:
Inicio rápido (igual que la información anterior) : Aprenda los conceptos básicos de comenzar con la biblioteca antes de sumergirse en todas sus características.
¿Qué son las dependencias? : Aprenda qué dependencias son, cómo complican su código y por qué quiere controlarlas.
Uso de dependencias : aprenda a usar las dependencias registradas en la biblioteca.
Registro de dependencias : aprenda cómo registrar sus propias dependencias en la biblioteca para que estén disponibles de inmediato en cualquier parte de su base de código.
En vivo, vista previa y dependencias de pruebas : aprenda cómo proporcionar diferentes implementaciones de sus dependencias para su uso en la aplicación en vivo, así como en las vistas previas de Xcode, e incluso en las pruebas.
Pruebas : una de las principales razones para controlar las dependencias es permitir una prueba más fácil. Aprenda algunos consejos y trucos para escribir mejores pruebas con la biblioteca.
Diseño de dependencias : aprenda técnicas sobre el diseño de sus dependencias para que sean más flexibles para inyectar características y anular las pruebas.
Dependencias primordiales : aprenda cómo se pueden cambiar las dependencias en tiempo de ejecución para que ciertas partes de su aplicación puedan usar diferentes dependencias.
Vida de dependencia : aprenda sobre las vidas de las dependencias, cómo prolongar la vida útil de una dependencia y cómo se heredan las dependencias.
Sistemas de punto de entrada único : aprenda sobre los sistemas de "punto de entrada único" y por qué son los más adecuados para esta biblioteca de dependencias, aunque es posible usar la biblioteca con sistemas de puntos de entrada no soleados.
Reconstruimos la aplicación de demostración de Scrumdinger de Apple utilizando las mejores prácticas modernas para el desarrollo de Swiftui, incluido el uso de esta biblioteca para controlar las dependencias en el acceso al sistema de archivos, los temporizadores y las API de reconocimiento de voz. Esa demostración se puede encontrar aquí.
La última documentación para las API de dependencias está disponible aquí.
Puede agregar dependencias a un proyecto XCode agregándolo a su proyecto como paquete.
https://github.com/pointfreeco/swift-dependences
Si desea usar dependencias en un proyecto SWIFTPM, es tan simple como agregarlo a su Package.swift
:
dependencies: [
. package ( url : " https://github.com/pointfreeco/swift-dependencies " , from : " 1.0.0 " )
]
Y luego agregar el producto a cualquier objetivo que necesite acceso a la biblioteca:
. product ( name : " Dependencies " , package : " swift-dependencies " ) ,
Si desea discutir esta biblioteca o tiene una pregunta sobre cómo usarla para resolver un problema en particular, hay una serie de lugares que puede discutir con otros entusiastas de puntuación:
Esta biblioteca controla una serie de dependencias de la caja, pero también está abierta a la extensión. Todos los siguientes proyectos se construyen en la parte superior de las dependencias:
Hay muchas otras bibliotecas de inyección de dependencia en la comunidad Swift. Cada uno tiene su propio conjunto de prioridades y compensaciones que difieren de las dependencias. Aquí hay algunos ejemplos bien conocidos:
Esta biblioteca se lanza bajo la licencia MIT. Vea la licencia para más detalles.