{"id":13823445,"url":"https://github.com/tgrapperon/swift-dependencies-additions","last_synced_at":"2025-04-05T14:09:43.740Z","repository":{"id":65192781,"uuid":"581857020","full_name":"tgrapperon/swift-dependencies-additions","owner":"tgrapperon","description":"More dependencies for `swift-dependencies` ","archived":false,"fork":false,"pushed_at":"2024-08-15T17:23:49.000Z","size":551,"stargazers_count":311,"open_issues_count":11,"forks_count":39,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-04-04T14:47:52.239Z","etag":null,"topics":["apple-framework","architecture","dependencies","dependency-injection","swift","testing"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tgrapperon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["tgrapperon"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2022-12-24T15:55:27.000Z","updated_at":"2025-03-22T13:41:25.000Z","dependencies_parsed_at":"2024-11-21T21:13:09.653Z","dependency_job_id":null,"html_url":"https://github.com/tgrapperon/swift-dependencies-additions","commit_stats":{"total_commits":80,"total_committers":11,"mean_commits":"7.2727272727272725","dds":"0.13749999999999996","last_synced_commit":"7b1d88ec23f80b0b5d4832d51af7647fa5062d8e"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgrapperon%2Fswift-dependencies-additions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgrapperon%2Fswift-dependencies-additions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgrapperon%2Fswift-dependencies-additions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgrapperon%2Fswift-dependencies-additions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tgrapperon","download_url":"https://codeload.github.com/tgrapperon/swift-dependencies-additions/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247345856,"owners_count":20924102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple-framework","architecture","dependencies","dependency-injection","swift","testing"],"created_at":"2024-08-04T09:00:34.571Z","updated_at":"2025-04-05T14:09:43.723Z","avatar_url":"https://github.com/tgrapperon.png","language":"Swift","funding_links":["https://github.com/sponsors/tgrapperon"],"categories":["Uncategorized","Swift"],"sub_categories":["Uncategorized"],"readme":"# Dependencies Additions\n\nA companion library to Point-Free's [`swift-dependencies`](https://github.com/pointfreeco/swift-dependencies) that provides higher-level dependencies.\n\n[![CI](https://github.com/tgrapperon/swift-dependencies-additions/actions/workflows/ci.yml/badge.svg)](https://github.com/tgrapperon/swift-dependencies-additions/actions/workflows/ci.yml)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ftgrapperon%2Fswift-dependencies-additions%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/tgrapperon/swift-dependencies-additions)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ftgrapperon%2Fswift-dependencies-additions%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/tgrapperon/swift-dependencies-additions)\n\n## On the menu\n[Dependencies](https://github.com/pointfreeco/swift-dependencies) is a fantastic library that helps you to manage your dependencies in a similar fashion SwiftUI handles its `Environment`. `Dependencies` already ships with many built-in fundamental dependencies, like `clock`, `uuid`, `date`, etc.\n\n\"Dependencies Additions\" intends to extend these core dependencies, and provide coherent and testable implementations to many additional dependencies that are commonly needed when developing on Apple's platforms.\n\nThe library currently proposes a few low-level dependencies to interface with:\n- `Accessibility`, an abstraction over `UIAccessibility`;\n- `Application`, an abstraction over `UIApplication.shared`;\n- ~`AssertionDependency`, to abstract `assert(…)` calls and promote them to failures when testing;~ \n(directly available in `Dependencies` starting from v1.3.0)\n- `BundleInfo`, an abstraction over the app's `info.plist`;\n- `Codable`, to encode/decode `Codable` types to `Data`;\n- `Compression`, to compress/decompress `Data` using the `Compression framework;\n- `DataReader/Writer`, to read/write `Data` from `URL`'s (an idea from [David Roman](https://github.com/davdroman));\n- `Logger`, that exposes a privacy-aware `Logger` instance;\n- `NotificationCenter`;\n- `PersistentContainer`, that abstracts a CoreData `NSPersistentContainer`;\n- `UserDefaults`;\n- `UserNotificationCenter`;\n- `Path`, a generalized collection of `AnyHashable`, to which you can push and pop identifiers to contextualize your models;\n- `ProcessInfo`;\n- `Device` (`UIDevice`, `WKInterfaceDevice`, `DCDevice`,…).\n\nIt also ships with more experimental and higher-level abstractions for:\n- `AppStorage`, which proposes a `@Dependency.AppStorage` property wrapper that mimics `SwiftUI`s `@AppStorage`, but usable from your model and or any concurrent context.\n- `CoreData`, which attempts to expose a safe and convenient interface to your `CoreData` graph (WIP).\n- `Notification`, that exposes `NotificationCenter`'s notifications under the form of typed and controllable `AsyncSequence`s.\n- `SwiftUI`'s `Environment`, which republishes `SwiftUI`'s `Environment` values in your model.\n\nThese higher-level dependencies are currently all experimental, and their targets are named with underscores.\nThey could eventually evolve out of `Dependencies Additions` into dedicated repositories if their size/behavior justifies it.\n\nThis library also proposes a few direct extensions to \"core\" dependencies like some new date and random numbers generators, as well as some tools to help mixing `AsyncSequence`s with Combine for example.\n\nThis list is preliminary, and many new dependencies will be added to this library in the upcoming weeks.\nIf you need one specific dependency, feel free to open a discussion, so we can find the better way it can\nintegrate with the other ones.\n\n## How to use `Dependencies Additions`?\n\nThis library proposes many heterogeneous dependencies. Having all of them bundled under the same repository has many benefits:\n- All the dependencies API's are designed coherently, with predictable behaviors.\n- Some dependencies are too small to justify a fully-fledged repository. Having all of them at hand helps with discovery.\n- Some dependencies depend on other dependencies, and it would be much more complex to manage if each project is in a dedicated repository.\n\nYou can simply import `DependenciesAdditions` umbrella product to get access to all the dependencies at once \nIf you prefer more control, and because each dependency of them is self-contained in its own module, you can import only the ones that you need \"à la carte\", on a file-by-file basis.\n\n### Using Xcode packages dependencies:\n\nAdd the `swift-dependencies-additions` package, and only select \"DependenciesAdditions\" product\n\n### Using SwiftPM:\n\nIn the `dependencies` section, add:\n```swift\n.package(url: \"https://github.com/tgrapperon/swift-dependencies-additions\", from: \"0.1.0\")\n```\nIn each module you need access to these dependencies, add:\n```swift\n.target(\n  name: \"MyModule\",\n  dependencies: [\n    .product(name: \"DependenciesAdditions\", package: \"swift-dependencies-additions\")\n  ]\n),\n```\nThis gives access to all non-underscored dependencies. Experimental dependencies need to be imported individually. For example:\n```swift\n.product(name: \"_AppStorage\", package: \"swift-dependencies-additions\")\n```\n\n## A quick tour of the dependencies\n\nWe present here a few of the dependencies currently shipping with the library.\nIf you're more interested in experimental abstractions like `AppStorage` or typed `Notification`, you can directly jump to the [Higher-level dependencies](#higher-level-dependencies) section.\n\n### Application\n\nAn abstraction over `UIApplication` that you can use to communicate with your app's instance.\n\nFor example:\n```swift\nclass Model {\n  @Dependency(\\.application) var application\n\n  func setAlternateIcon(name: String) async throws {\n    try await self.application.setAlternateIconName(name)\n  }\n}\n```\n\nAnd then, when testing:\n```swift\n@MainActor \nfunc testAlternateIconIsSet() async throws -\u003e Void {\n  var alternateIconName = LockIsolated(\"\")\n  let model = withDependencies {\n    $0.application.$setAlternateIcon = { name in\n      alternateIconName.withValue { $0 = name }\n    }\n  } operation: { Model() }\n  try await model.setAlternateIcon(name: \"blueprint\")\n  XCTAssertEqual(alternateIconName.value, \"blueprint\")\n} \n```\n\n### Accessibility\n\nAn abstraction over `UIAccessibility` that you can use to monitor the accessibility state of\nyour app's instance.\n\nFor example:\n```swift\nclass Model {\n  @Dependency(\\.accessibility.isClosedCaptioningEnabled) var isClosedCaptioningEnabled\n\n  func play() -\u003e Void {\n    if self.isClosedCaptioningEnabled {\n      self.updateClosedCaptions()\n    }\n  }\n}\n```\n\n### BundleInfo\n\nThis simple dependency exposes a `BundleInfo` type that allows to simply retrieve a few `info.plist`-related fields, like the `bundleIdentifier` or the app's `version`. \n\nFor example:\n```swift\n@Dependency(\\.bundleInfo.bundleIdentifier) var bundleIdentifier\n```\nAs this value is often used to prefix identifiers, having this value exposed as a dependency allows you to control it at a distance when testing for example.\n\n### Codable\nThe library exposes two dependencies to help with coding or decoding your `Codable` types.\n```swift\n@Dependency(\\.encode) var encode\n@Dependency(\\.decode) var decode\n\nstruct Point: Codable {\n  var x: Double\n  var y: Double\n}\n\nlet point = Point(x: 12, y: 35)\nlet encoded = try encode(point) // A `Data` value\nlet decoded = try decode(Point.self, from: encoded) // A `Point` value\n```\nAs you can see, the API is very similar to JSON or PropertyList encoder and decoder.\n\nBy default, `encode` and `decode` are producing/consuming `JSON` data.\n\n### Compression\nIn the same fashion as `encode` and `decode`, the library exposes two\ndependencies to compress and decompress `Data`, using Apple's Compression framework:\n```swift\n@Dependency(\\.compress) var compress\n@Dependency(\\.decompress) var decompress\n\nlet uncompressed = \"Lorem ipsum dolor sit amet\".data(using: .utf8)!\nlet compressed = try compress(uncompressed, using: .lzfse)\nlet decompressed = try decompress(compressed, using: .lzfse)\n```\nThey can also be called from async contexts, where a more efficient variant is used:\n```swift\nlet compressed = try await compress(uncompressed)\nlet decompressed = try await decompress(compressed)\n```\n\nBy default, `compress` and `decompress` are using the `.zlib` algorithm.\n\n### Logger\nThis dependency exposes a privacy-aware `Logger` instance.\n@Dependency(\\.logger) var logger\n\nYou can simply use it as\n```swift\nlogger.log(level: .info, \"User with id: \\(userID, privacy: .private) did purchase a smoothie\")\n```\nYou can simply create a subsystem using the provided subscript:\n```swift\n@Dependency(\\.logger[\"Transactions\"]) var transactionsLogger\n```\n\n### PersistentContainer\nA `NSPersistentContainer` that exposes Core Data `NSManagedObjectContext`s. You can use it as a \nbasis for more elaborate abstractions.\n```swift\n@Dependency(\\.persistentContainer) var persistentContainer\n```\nBy default, the preview version is an `in-memory` variant, and you can easily setup mocks for your SwiftUI previews:\n```swift\nvar previews: some View {\n  let model = withDependencies {\n    $0.persistentContainer = .default(inMemory: true).with { context in\n      let smoothie = Smoothie(context: context)\n      smoothie.flavor = \"Banana\"\n    }\n  }\n  SmoothieView(model: model)\n}\n```\n\n### ProcessInfo\nA simple abstraction over `ProcessInfo` that allows to retrieve low-level information on the system.\n```swift\n@Dependency(\\.processInfo.thermalState) var thermalState\n\nif thermalState == .critical {\n  self.disableFancyAnimations()\n}\n```\nBecause it's a dependency, you can test it very easily without having to modify your model.\n\n### UserDefaults\nAn abstraction over `UserDefaults`, where you can read and save from the user preferences.\nThe library exposes the same types as SwiftUI's AppStorage, so you can simply store and retrieve \nyour data. \n```swift\n@Dependency(\\.userDefaults) var userDefaults\n\nuserDefaults.set(true, forKey: \"hasUserPassedOnboarding\")\n```\nWith one line of code, you can make your whole app write to your app group user defaults, an in-memory version for testing, or even to `NSUbiquitousKeyValueStore` that syncs user preferences over iCloud.\n\nYou can also give a spin to the more powerful [`_AppStorage`](#appstorage) dependency that is built on top of `\\.userDefaults`, and which allows to seamlessly observe and assign user preferences with an API similar to SwiftUI's `AppStorage` (with which it can interoperate).\n\n### Other dependencies\nMany other dependencies are available, like `UserNotifications` to display notifications, `Device` to interact with `UIDevice` or `WKInterfaceDevice`, `Path` to contextualize your model's tree, a clicking `DateGenerator` that is controlled by a `Clock` (that you can control itself), etc.\n\nOf course, this is only the beginning and many other dependencies will be added in the upcoming weeks.\nWe strongly feel that the larger the dependencies spectrum is, the more you will use them, and the more your code will be testable and structured.\n\n## Higher-level dependencies\nThe library proposes a few experimental higher-level dependencies. They are currently \"underscored\", meaning that their APIs are not finalized. They may be extracted into their own library in the future.\n\n### AppStorage\n```swift\n@Dependency.AppStorage(\"username\") var username: String = \"Anonymous\"\n```\nThe API follows SwiftUI's `AppStorage`, but is backed by `@Dependency(\\.userDefaults)`.\nIt can operate within your model and be accessed from async contexts. If the same `key`s are used, it can inter-operate with `SwiftUI`'s own `AppStorage`.\nThe projected value is an `AsyncStream\u003cValue\u003e` of this user preference's values. They can be observed from any async context:\n```swift\n@Dependency.AppStorage(\"isSoundEnabled\") var isSoundEnabled: Bool = false\n\nfor await isSoundEnabled in $isSoundEnabled {\n  await isSoundEnabled ? audioEngine.start() : audioEngine.stop()\n}\n```\n### Notifications\nThis dependency allows exposing `Notification`s as typed `AsyncSequence`s.\n```swift\nextension Notifications {\n  /// A typed `Notification` that publishes the current device's battery level.\n  @MainActor\n  public var batterLevelDidChange: SystemNotificationOf\u003cFloat\u003e {\n    .init(UIDevice.batteryLevelDidChangeNotification) { notification in\n      @Dependency(\\.device.batteryLevel) var level;\n      return level\n    }\n  }\n}\n```\nYou can then expose this notification with a dedicated property wrapper:\n```swift\n@Dependency.Notification(\\.batteryLevelDidChange) var batteryLevel\n```\nThe exposed value is an async sequence of `Float` representing the `batteryLevel`:\n```swift\nfor await level in batteryLevel {\n  if level \u003c 0.2 {\n    self.isLowPowerModeEnabled = true\n  }\n}\n```\n### SwiftUI Environment\nThis dependency brings SwiftUI's `Environment` into your model:\n```swift\n@Dependency.Environment(\\.colorScheme) var colorScheme\n@Dependency.Environment(\\.dismiss) var dismiss\n```\nThen, in any `View`, you use the `.observeEnvironmentAsDependency(\\.colorScheme)` modifier to \nbubble up this value into the model:\n```swift\nHStack { … }\n  .observeEnvironmentAsDependency(\\.colorScheme)\n  .observeEnvironmentAsDependency(\\.dismiss)\n```\nIn the example above, `self.colorScheme` is a `ColorScheme?`, and `self.dismissAction` is a \n`DismissAction?`. Both are optional because they're conditioned by the existence of the `View`, and\nthey can become `nil` again if this view goes away.\nYou can observe their value through the projected value which is an `AsyncSequence` of the wrapped\nvalue:\n```swift\nfor await colorScheme in self.$colorScheme.compactMap{ $0 }.dropFirst() {\n  self.logger.info(\"ColorScheme did change: \\(colorScheme)\")\n}\n```\n### Core Data (WIP)\nThis dependency is still WIP because we would like to harden the API to avoid common pitfalls with CoreData.\nBut you can get an excerpt of it in the CoreData CaseStudy!\n\n## What's next?\n\nThis is only the beginning! There are many other dependencies to implement: `Speech`, `Vision`, `KeyChain`, etc…\nThe only rule, for now, is that it shouldn't require a third-party dependency itself, and should work\non `Apple` or `Linux` platforms out of the box.\nIf you want to contribute a dependency, feel free to open a thread in the discussions!\n\n## Installation\n\nYou can add DependenciesAdditions to an Xcode project by adding it to your project as a package.\n```\nhttps://github.com/tgrapperon/swift-dependencies-additions\n```\n\nIf you want to use DependenciesAdditions in a SwiftPM project, it's as simple as adding it to your Package.swift:\n```swift\ndependencies: [\n  .package(url: \"https://github.com/tgrapperon/swift-dependencies-additions\", from: \"1.0.0\")\n]\n```\n\n## License\nThis library is released under the MIT license. See LICENSE for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftgrapperon%2Fswift-dependencies-additions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftgrapperon%2Fswift-dependencies-additions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftgrapperon%2Fswift-dependencies-additions/lists"}