依赖项扩展
这是Point-Free的swift-dependencies
库的配套库,提供了更高级别的依赖项。
目录
Dependencies是一个出色的库,它帮助你以类似SwiftUI处理其Environment
的方式管理你的依赖项。Dependencies
已经自带了许多内置的基础依赖项,如clock
、uuid
、date
等。
"Dependencies Additions"旨在扩展这些核心依赖项,并为在Apple平台上开发时常需要的许多额外依赖项提供一致且可测试的实现。
该库目前提供了一些低级别的依赖项,用于与以下内容进行接口:
Accessibility
,UIAccessibility
的抽象;Application
,UIApplication.shared
的抽象;(从v1.3.0版本开始直接在AssertionDependency
,用于抽象assert(…)
调用,并在测试时将其提升为失败;Dependencies
中可用)BundleInfo
,应用程序info.plist
的抽象;Codable
,用于将Codable
类型编码/解码为Data
;Compression
,使用Compression
框架压缩/解压缩Data
;DataReader/Writer
,用于从URL
读取/写入Data
(来自David Roman的想法);Logger
,暴露一个注重隐私的Logger
实例;NotificationCenter
;PersistentContainer
,抽象了CoreData的NSPersistentContainer
;UserDefaults
;UserNotificationCenter
;Path
,一个通用的AnyHashable
集合,你可以向其中推送和弹出标识符以上下文化你的模型;ProcessInfo
;Device
(UIDevice
、WKInterfaceDevice
、DCDevice
等)。
它还附带了一些更实验性和更高级别的抽象:
AppStorage
,提供了一个@Dependency.AppStorage
属性包装器,模仿SwiftUI
的@AppStorage
,但可以在你的模型和任何并发上下文中使用。CoreData
,尝试为你的CoreData
图提供一个安全方便的接口(开发中)。Notification
,以类型化和可控的AsyncSequence
形式暴露NotificationCenter
的通知。- SwiftUI的
Environment
,在你的模型中重新发布SwiftUI
的Environment
值。
这些高级依赖项目前都是实验性的,它们的目标名称带有下划线。
如果它们的规模/行为合理,它们最终可能会从Dependencies Additions
发展成独立的仓库。
这个库还为"核心"依赖项提供了一些直接扩展,如一些新的日期和随机数生成器,以及一些帮助混合AsyncSequence
和Combine的工具。
这个列表是初步的,在接下来的几周内,许多新的依赖项将被添加到这个库中。 如果你需要某个特定的依赖项,请随时开启讨论,这样我们可以找到更好的方式将其与其他依赖项整合。
如何使用Dependencies Additions
?
这个库提供了许多异构的依赖项。将它们全部捆绑在同一个仓库下有许多好处:
- 所有依赖项的API都经过统一设计,具有可预测的行为。
- 有些依赖项太小,不足以justification拥有一个完整的仓库。将它们集中在一起有助于发现。
- 某些依赖项依赖于其他依赖项,如果每个项目都在独立的仓库中,管理起来会复杂得多。
你可以简单地导入DependenciesAdditions
伞形产品,一次性获得所有依赖项的访问权限。
如果你更喜欢更多的控制,由于每个依赖项都被封装在自己的模块中,你可以按需"à la carte"地逐个文件导入你需要的依赖项。
使用Xcode包依赖:
添加swift-dependencies-additions
包,并只选择"DependenciesAdditions"产品
使用SwiftPM:
在dependencies
部分添加:
.package(url: "https://github.com/tgrapperon/swift-dependencies-additions", from: "0.1.0")
在每个需要访问这些依赖项的模块中,添加:
.target(
name: "MyModule",
dependencies: [
.product(name: "DependenciesAdditions", package: "swift-dependencies-additions")
]
),
这将提供对所有非下划线依赖项的访问。实验性依赖项需要单独导入。例如:
.product(name: "_AppStorage", package: "swift-dependencies-additions")
依赖项快速浏览
我们在这里介绍一些当前随库提供的依赖项。
如果你对AppStorage
或类型化Notification
等实验性抽象更感兴趣,你可以直接跳到高级依赖项部分。
Application
UIApplication
的抽象,你可以用它来与你的应用实例通信。
例如:
class Model {
@Dependency(\.application) var application
func setAlternateIcon(name: String) async throws {
try await self.application.setAlternateIconName(name)
}
}
然后,在测试时:
@MainActor
func testAlternateIconIsSet() async throws -> Void {
var alternateIconName = LockIsolated("")
let model = withDependencies {
$0.application.$setAlternateIcon = { name in
alternateIconName.withValue { $0 = name }
}
} operation: { Model() }
try await model.setAlternateIcon(name: "blueprint")
XCTAssertEqual(alternateIconName.value, "blueprint")
}
Accessibility
UIAccessibility
的抽象,你可以用它来监控你的应用实例的无障碍状态。
例如:
class Model {
@Dependency(\.accessibility.isClosedCaptioningEnabled) var isClosedCaptioningEnabled
func play() -> Void {
if self.isClosedCaptioningEnabled {
self.updateClosedCaptions()
}
}
}
BundleInfo
这个简单的依赖项暴露了一个BundleInfo
类型,允许简单地检索一些与info.plist
相关的字段,如bundleIdentifier
或应用的version
。
例如:
@Dependency(\.bundleInfo.bundleIdentifier) var bundleIdentifier
由于这个值经常用于前缀标识符,将这个值作为依赖项暴露允许你在测试时远程控制它。
Codable
该库暴露了两个依赖项来帮助编码或解码你的Codable
类型。
@Dependency(\.encode) var encode
@Dependency(\.decode) var decode
struct Point: Codable {
var x: Double
var y: Double
}
let point = Point(x: 12, y: 35)
let encoded = try encode(point) // 一个`Data`值
let decoded = try decode(Point.self, from: encoded) // 一个`Point`值
如你所见,API与JSON或PropertyList编码器和解码器非常相似。
默认情况下,encode
和decode
产生/消费JSON
数据。
Compression
与encode
和decode
类似,该库暴露了两个依赖项来压缩和解压缩Data
,使用Apple的Compression框架:
@Dependency(\.compress) var compress
@Dependency(\.decompress) var decompress
let uncompressed = "Lorem ipsum dolor sit amet".data(using: .utf8)!
let compressed = try compress(uncompressed, using: .lzfse)
let decompressed = try decompress(compressed, using: .lzfse)
它们也可以从异步上下文调用,在那里使用更高效的变体:
let compressed = try await compress(uncompressed)
let decompressed = try await decompress(compressed)
默认情况下,compress
和decompress
使用.zlib
算法。
Logger
这个依赖项暴露了一个注重隐私的Logger
实例。
@Dependency(.logger) var logger
你可以简单地使用它:
logger.log(level: .info, "User with id: \(userID, privacy: .private) did purchase a smoothie")
你可以使用提供的下标简单地创建一个子系统:
@Dependency(\.logger["Transactions"]) var transactionsLogger
PersistentContainer
一个暴露Core Data NSManagedObjectContext
的NSPersistentContainer
。你可以用它作为更复杂抽象的基础。
@Dependency(\.persistentContainer) var persistentContainer
默认情况下,预览版本是一个内存
变体,你可以轻松为SwiftUI预览设置模拟:
var previews: some View {
let model = withDependencies {
$0.persistentContainer = .default(inMemory: true).with { context in
let smoothie = Smoothie(context: context)
smoothie.flavor = "Banana"
}
}
SmoothieView(model: model)
}
ProcessInfo
ProcessInfo
的简单抽象,允许检索系统的低级信息。
@Dependency(\.processInfo.thermalState) var thermalState
if thermalState == .critical {
self.disableFancyAnimations()
}
因为它是一个依赖项,你可以很容易地测试它,而不必修改你的模型。
UserDefaults
UserDefaults
的抽象,你可以从用户偏好中读取和保存。
该库暴露了与SwiftUI的AppStorage相同的类型,因此你可以简单地存储和检索你的数据。
@Dependency(\.userDefaults) var userDefaults
userDefaults.set(true, forKey: "hasUserPassedOnboarding")
只需一行代码,你就可以让你的整个应用程序写入你的应用程序组用户默认值,用于测试的内存版本,甚至是通过iCloud同步用户偏好的NSUbiquitousKeyValueStore
。
你还可以尝试使用基于\.userDefaults
构建的更强大的_AppStorage
依赖,它允许使用类似于SwiftUI的AppStorage
的API无缝观察和分配用户首选项(可以与之互操作)。
其他依赖项
还有许多其他可用的依赖项,比如用于显示通知的UserNotifications
,用于与UIDevice
或WKInterfaceDevice
交互的Device
,用于上下文化模型树的Path
,由Clock
(你可以控制)控制的点击DateGenerator
等。
当然,这只是开始,在接下来的几周内还会添加许多其他依赖项。 我们强烈认为,依赖项范围越广,你就越会使用它们,你的代码就越容易测试和结构化。
高级依赖项
该库提供了一些实验性的高级依赖项。它们目前是"带下划线的",意味着它们的API尚未最终确定。将来可能会将它们提取到自己的库中。
AppStorage
@Dependency.AppStorage("username") var username: String = "Anonymous"
API遵循SwiftUI的AppStorage
,但由@Dependency(\.userDefaults)
支持。
它可以在你的模型中运行,并可从异步上下文访问。如果使用相同的key
,它可以与SwiftUI
自己的AppStorage
互操作。
投影值是此用户首选项值的AsyncStream<Value>
。可以从任何异步上下文观察它们:
@Dependency.AppStorage("isSoundEnabled") var isSoundEnabled: Bool = false
for await isSoundEnabled in $isSoundEnabled {
await isSoundEnabled ? audioEngine.start() : audioEngine.stop()
}
Notifications
此依赖项允许将Notification
作为类型化的AsyncSequence
公开。
extension Notifications {
/// 发布当前设备电池电量的类型化`Notification`。
@MainActor
public var batterLevelDidChange: SystemNotificationOf<Float> {
.init(UIDevice.batteryLevelDidChangeNotification) { notification in
@Dependency(\.device.batteryLevel) var level;
return level
}
}
}
然后你可以使用专用属性包装器公开此通知:
@Dependency.Notification(\.batteryLevelDidChange) var batteryLevel
公开的值是表示batteryLevel
的Float
类型的异步序列:
for await level in batteryLevel {
if level < 0.2 {
self.isLowPowerModeEnabled = true
}
}
SwiftUI Environment
此依赖项将SwiftUI的Environment
引入你的模型:
@Dependency.Environment(\.colorScheme) var colorScheme
@Dependency.Environment(\.dismiss) var dismiss
然后,在任何View
中,使用.observeEnvironmentAsDependency(\.colorScheme)
修饰符将此值冒泡到模型中:
HStack { … }
.observeEnvironmentAsDependency(\.colorScheme)
.observeEnvironmentAsDependency(\.dismiss)
在上面的例子中,self.colorScheme
是ColorScheme?
,self.dismissAction
是DismissAction?
。两者都是可选的,因为它们的存在取决于View
的存在,如果这个视图消失,它们可能会再次变为nil
。
你可以通过投影值观察它们的值,投影值是包装值的AsyncSequence
:
for await colorScheme in self.$colorScheme.compactMap{ $0 }.dropFirst() {
self.logger.info("ColorScheme did change: \(colorScheme)")
}
Core Data(进行中)
这个依赖项仍在进行中,因为我们想要加强API以避免CoreData常见的陷阱。 但你可以在CoreData案例研究中看到它的一个摘录!
接下来是什么?
这只是开始!还有许多其他依赖项需要实现:Speech
、Vision
、KeyChain
等…
目前唯一的规则是它不应该自身需要第三方依赖,并且应该在Apple
或Linux
平台上开箱即用。
如果你想贡献一个依赖项,欢迎在讨论中开启一个主题!
安装
你可以通过将DependenciesAdditions作为包添加到项目中来将其添加到Xcode项目中。
https://github.com/tgrapperon/swift-dependencies-additions
如果你想在SwiftPM项目中使用DependenciesAdditions,只需将其添加到你的Package.swift中即可:
dependencies: [
.package(url: "https://github.com/tgrapperon/swift-dependencies-additions", from: "1.0.0")
]
许可证
该库根据MIT许可证发布。有关详细信息,请参阅LICENSE。