依赖性管理库,灵感来自Swiftui的“环境”。
该图书馆的动机和设计在许多无点上的情节中,这是一个视频系列,探索了功能编程和由布兰登·威廉姆斯(Brandon Williams)和斯蒂芬·塞利斯(Stephen Celis)主持的Swift语言。
依赖项是您应用程序中需要与您无法控制的外部系统进行交互的类型和功能。此类的经典示例是向服务器提出网络请求的API客户端,但也可以将诸如UUID
和Date
初始化器,文件访问,用户默认值甚至时钟和计时器之类的内容视为依赖关系。
您可以在应用程序开发的情况下真正走得很远,而无需考虑依赖性管理(或者,某些人喜欢称其为“依赖注入”),但是最终不受控制的依赖关系可能会在您的代码基础和开发周期中引起许多问题:
不受控制的依赖性使得很难快速编写,确定性的测试,因为您容易受到外界的变化的影响,例如文件系统,网络连接,互联网速度,服务器正常运行时间等。
许多依赖项在SwiftUI预览中无法很好地工作,例如位置经理和语音认可者,即使在模拟器(例如运动经理)等中,有些依赖项也无法正常工作。如果您使用这些框架,这使您无法轻松迭代功能的设计。
与第三方,非应用程序库(例如Firebase,Web插座库,网络库等)相互作用的依赖关系往往是重量级的,并且需要很长时间才能编译。这可以减慢您的开发周期。
由于这些原因,还有很多事情强烈鼓励您控制自己的依赖关系,而不是让他们控制您。
但是,控制依赖性只是开始。一旦控制了依赖项,就面临着一系列新问题:
在整个应用程序中,您如何以更符合人体工程学的方式来传播依赖关系,而不是明确地将它们传播到任何地方,而是比拥有全球依赖性更安全?
您如何仅在应用程序的一部分中超越依赖项?这对于测试和SwiftUI预览的重大依赖性以及特定的用户流(例如入职经验)可能会很方便。
您如何确定您将功能在测试中使用的所有依赖性占据了所有依赖性?测试嘲笑一些依赖性,但将其他人与外界互动是不正确的。
该库解决了上面的所有要点,还有更多。
该库允许您注册自己的依赖项,但它也带有许多可控的依赖关系(请参阅完整列表的DependencyValues
),并且很有可能您可以立即使用一个。如果您使用Date()
, UUID()
, Task.sleep
或直接在功能逻辑中组合调度程序,则可以开始使用此库。
@ 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
// ...
}
一旦声明了依赖项,而不是直接接触Date()
, UUID()
等,您可以使用功能模型上定义的依赖项:
@ 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()'
)
)
}
}
这就是开始在功能中使用可控依赖项所需的全部。经过一小部分的前期工作,您可以开始利用图书馆的权力。
例如,您可以轻松地控制测试中的这些依赖项。如果要测试addButtonTapped
方法中的逻辑,则可以使用withDependencies
函数来覆盖一个单个测试范围的任何依赖项。就像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 )
)
]
)
}
在这里,我们控制了date
依赖性始终返回同一日期,我们控制了uuid
依赖性,每次调用它时都会返回自动提出的UUID,甚至我们甚至使用ImmediateClock
电信来控制clock
依赖性立即的。如果我们不控制这些依赖关系,那么本测试将很难编写,因为没有办法准确预测Date()
和UUID()
将返回的内容,我们必须等待现实世界的过去时间,使测试缓慢。
但是,可控的依赖性不仅对测试有用。它们也可以在Xcode预览中使用。假设上面的功能会使用时钟睡一段时间才能在视图中发生。如果您不想等待时间流逝以查看视图的变化,则可以使用.dependencies
PREVIEW特征来覆盖时钟依赖性为“立即”时钟:
#Preview (
traits : . dependencies {
$0 . continuousClock = . immediate
}
) {
// All access of '@Dependency(.continuousClock)' in this preview will
// use an immediate clock.
FeatureView ( model : FeatureModel ( ) )
}
这将使预览在运行时使用即时时钟,但是在模拟器或设备上运行时,它仍将使用实时连续ContinuousClock
。这使得仅用于预览而不影响您的应用程序在生产中的运行方式而成为可能。
这是开始使用库的基础知识,但是您还有很多事情可以做。您可以通过探索文档和文章来深入了解图书馆:
快速启动(与上面的信息相同) :在深入研究其所有功能之前,请学习从图书馆开始的基础知识。
什么是依赖性? :了解什么是依赖性,它们如何使您的代码复杂化以及为什么要控制它们。
使用依赖项:了解如何使用在库中注册的依赖项。
注册依赖项:了解如何在库中注册自己的依赖项,以便它们立即从代码库中的任何部分可用。
实时,预览和测试依赖性:了解如何提供在实时应用程序中以及XCode预览甚至测试中使用的依赖项的不同实现。
测试:控制依赖性的主要原因之一是允许更轻松的测试。学习一些技巧和技巧,以编写图书馆更好的测试。
设计依赖项:学习设计依赖性的技术,以便它们最灵活地注入功能并覆盖测试。
重大依赖性:了解如何在运行时更改依赖项,以便应用程序的某些部分可以使用不同的依赖关系。
依赖性寿命:了解依赖性的寿命,如何延长依赖的寿命以及如何遗传依赖性。
单个入口点系统:了解“单个入口点”系统,以及为什么它们最适合此依赖项库,尽管可以将库与非单个入口点系统一起使用。
我们使用SwiftUI开发的现代最佳实践重建了Apple的Scrumdinger演示应用程序,包括使用此库来控制文件系统访问,计时器和语音识别API的依赖关系。该演示可以在这里找到。
有关依赖项API的最新文档可在此处找到。
您可以通过将其作为包装添加到项目中,将依赖项添加到Xcode项目中。
https://github.com/pointfreeco/swift-dependencies
如果您想在SwiftPM项目中使用依赖项,那么将其添加到Package.swift
中一样简单。
dependencies: [
. package ( url : " https://github.com/pointfreeco/swift-dependencies " , from : " 1.0.0 " )
]
然后将产品添加到需要访问库的任何目标:
. product ( name : " Dependencies " , package : " swift-dependencies " ) ,
如果您想讨论此图书馆或对如何使用它解决特定问题有疑问,那么您可以在许多地方与其他无点的爱好者讨论:
该库控制了一些依赖性,但也可以开放扩展。以下项目都建立在依赖项之上:
Swift社区中还有许多其他依赖注入库。每个都有自己的一套优先级和权衡,与依赖关系有所不同。这里有一些众所周知的例子:
该库是根据麻省理工学院许可证发布的。有关详细信息,请参见许可证。