• Stars
    star
    1,322
  • Rank 34,138 (Top 0.7 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 1 year ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A dependency management library inspired by SwiftUI's "environment."

Dependencies

A dependency management library inspired by SwiftUI's "environment."

CI Slack

Learn More

This library was motivated and designed over the course of many episodes on Point-Free, a video series exploring functional programming and the Swift language, hosted by Brandon Williams and Stephen Celis.

video poster image

Overview

Dependencies are the types and functions in your application that need to interact with outside systems that you do not control. Classic examples of this are API clients that make network requests to servers, but also seemingly innocuous things such as UUID and Date initializers, file access, user defaults, and even clocks and timers, can all be thought of as dependencies.

You can get really far in application development without ever thinking about dependency management (or, as some like to call it, "dependency injection"), but eventually uncontrolled dependencies can cause many problems in your code base and development cycle:

  • Uncontrolled dependencies make it difficult to write fast, deterministic tests because you are susceptible to the vagaries of the outside world, such as file systems, network connectivity, internet speed, server uptime, and more.

  • Many dependencies do not work well in SwiftUI previews, such as location managers and speech recognizers, and some do not work even in simulators, such as motion managers, and more. This prevents you from being able to easily iterate on the design of features if you make use of those frameworks.

  • Dependencies that interact with 3rd party, non-Apple libraries (such as Firebase, web socket libraries, network libraries, etc.) tend to be heavyweight and take a long time to compile. This can slow down your development cycle.

For these reasons, and a lot more, it is highly encouraged for you to take control of your dependencies rather than letting them control you.

But, controlling a dependency is only the beginning. Once you have controlled your dependencies, you are faced with a whole set of new problems:

  • How can you propagate dependencies throughout your entire application in a way that is more ergonomic than explicitly passing them around everywhere, but safer than having a global dependency?

  • How can you override dependencies for just one portion of your application? This can be handy for overriding dependencies for tests and SwiftUI previews, as well as specific user flows such as onboarding experiences.

  • How can you be sure you overrode all dependencies a feature uses in tests? It would be incorrect for a test to mock out some dependencies but leave others as interacting with the outside world.

This library addresses all of the points above, and much, much more.

Quick start

The library allows you to register your own dependencies, but it also comes with many controllable dependencies out of the box (see DependencyValues for a full list), and there is a good chance you can immediately make use of one. If you are using Date(), UUID(), Task.sleep, or Combine schedulers directly in your feature's logic, you can already start to use this library.

final class FeatureModel: ObservableObject {
  @Dependency(\.continuousClock) var clock  // Controllable way to sleep a task
  @Dependency(\.date.now) var now           // Controllable way to ask for current date
  @Dependency(\.mainQueue) var mainQueue    // Controllable scheduling on main queue
  @Dependency(\.uuid) var uuid              // Controllable UUID creation

  // ...
}

Once your dependencies are declared, rather than reaching out to the Date(), UUID(), etc., directly, you can use the dependency that is defined on your feature's model:

final class FeatureModel: ObservableObject {
  // ...

  func addButtonTapped() async throws {
    try await self.clock.sleep(for: .seconds(1))  // ๐Ÿ‘ˆ Don't use 'Task.sleep'
    self.items.append(
      Item(
        id: self.uuid(),  // ๐Ÿ‘ˆ Don't use 'UUID()'
        name: "",
        createdAt: self.now  // ๐Ÿ‘ˆ Don't use 'Date()'
      )
    )
  }
}

That is all it takes to start using controllable dependencies in your features. With that little bit of upfront work done you can start to take advantage of the library's powers.

For example, you can easily control these dependencies in tests. If you want to test the logic inside the addButtonTapped method, you can use the withDependencies function to override any dependencies for the scope of one single test. It's as easy as 1-2-3:

func testAdd() async throws {
  let model = withDependencies {
    // 1๏ธโƒฃ Override any dependencies that your feature uses.
    $0.clock = ImmediateClock()
    $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()
  XCTAssertEqual(
    model.items,
    [
      Item(
        id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!,
        name: "",
        createdAt: Date(timeIntervalSinceReferenceDate: 1234567890)
      )
    ]
  )
}

Here we controlled the date dependency to always return the same date, and we controlled the uuid dependency to return an auto-incrementing UUID every time it is invoked, and we even controlled the clock dependency using an ImmediateClock to squash all of time into a single instant. If we did not control these dependencies this test would be very difficult to write since there is no way to accurately predict what will be returned by Date() and UUID(), and we'd have to wait for real world time to pass, making the test slow.

But, controllable dependencies aren't only useful for tests. They can also be used in Xcode previews. Suppose the feature above makes use of a clock to sleep for an amount of time before something happens in the view. If you don't want to literally wait for time to pass in order to see how the view changes, you can override the clock dependency to be an "immediate" clock using the withDependencies helper:

struct Feature_Previews: PreviewProvider {
  static var previews: some View {
    FeatureView(
      model: withDependencies {
        $0.clock = ImmediateClock()
      } operation: {
        FeatureModel()
      }
    )
  }
}

This will make it so that the preview uses an immediate clock when run, but when running in a simulator or on device it will still use a live ContinuousClock. This makes it possible to override dependencies just for previews without affecting how your app will run in production.

That is the basics to getting started with using the library, but there is still a lot more you can do. You can learn more in depth about the library by exploring the documentation and articles:

Getting started

  • Quick start (Same as the information above): Learn the basics of getting started with the library before diving deep into all of its features.

  • What are dependencies?: Learn what dependencies are, how they complicate your code, and why you want to control them.

Essentials

  • Using dependencies: Learn how to use the dependencies that are registered with the library.

  • Registering dependencies: Learn how to register your own dependencies with the library so that they immediately become available from any part of your code base.

  • Live, preview, and test dependencies: Learn how to provide different implementations of your dependencies for use in the live application, as well as in Xcode previews, and even in tests.

  • Testing: One of the main reasons to control dependencies is to allow for easier testing. Learn some tips and tricks for writing better tests with the library.

Advanced

  • Designing dependencies: Learn techniques on designing your dependencies so that they are most flexible for injecting into features and overriding for tests.

  • Overriding dependencies: Learn how dependencies can be changed at runtime so that certain parts of your application can use different dependencies.

  • Dependency lifetimes: Learn about the lifetimes of dependencies, how to prolong the lifetime of a dependency, and how dependencies are inherited.

  • Single entry point systems: Learn about "single entry point" systems, and why they are best suited for this dependencies library, although it is possible to use the library with non-single entry point systems.

Examples

We rebuilt Apple's Scrumdinger demo application using modern, best practices for SwiftUI development, including using this library to control dependencies on file system access, timers and speech recognition APIs. That demo can be found here.

Documentation

The latest documentation for the Dependencies APIs is available here.

Installation

You can add Dependencies to an Xcode project by adding it to your project as a package.

https://github.com/pointfreeco/swift-dependencies

If you want to use Dependencies in a SwiftPM project, it's as simple as adding it to your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0")
]

And then adding the product to any target that needs access to the library:

.product(name: "Dependencies", package: "swift-dependencies"),

Community

If you want to discuss this library or have a question about how to use it to solve a particular problem, there are a number of places you can discuss with fellow Point-Free enthusiasts:

Extensions

This library controls a number of dependencies out of the box, but is also open to extension. The following projects all build on top of Dependencies:

Alternatives

There are many other dependency injection libraries in the Swift community. Each has its own set of priorities and trade-offs that differ from Dependencies. Here are a few well-known examples:

License

This library is released under the MIT license. See LICENSE for details.

More Repositories

1

swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
Swift
10,990
star
2

swift-snapshot-testing

๐Ÿ“ธ Delightful Swift snapshot testing.
Swift
3,559
star
3

isowords

Open source game built in SwiftUI and the Composable Architecture.
Swift
2,515
star
4

swiftui-navigation

Tools for making SwiftUI navigation simpler, more ergonomic and more precise.
Swift
1,654
star
5

swift-tagged

๐Ÿท A wrapper type for safer, expressive code.
Swift
1,289
star
6

swift-overture

๐ŸŽผ A library for function composition.
Swift
1,115
star
7

pointfreeco

๐ŸŽฌ The source for www.pointfree.co, a video series on functional programming and the Swift programming language.
Swift
1,054
star
8

episode-code-samples

๐Ÿ’พ Point-Free episode code.
Swift
922
star
9

swift-case-paths

๐Ÿงฐ Case paths extends the key path hierarchy to enum cases.
Swift
852
star
10

swift-nonempty

๐ŸŽ A compile-time guarantee that a collection contains a value.
Swift
817
star
11

swift-parsing

A library for turning nebulous data into well-structured data, with a focus on composition, performance, generality, and ergonomics.
Swift
802
star
12

swift-custom-dump

A collection of tools for debugging, diffing, and testing your application's data structures.
Swift
766
star
13

swift-html

๐Ÿ—บ A Swift DSL for type-safe, extensible, and transformable HTML documents.
Swift
723
star
14

combine-schedulers

โฐ A few schedulers that make working with Combine more testable and more versatile.
Swift
671
star
15

swift-web

๐Ÿ•ธ A collection of Swift server-side frameworks for handling HTML, CSS, routing and middleware.
Swift
477
star
16

swift-identified-collections

A library of data structures for working with collections of identifiable elements in an ergonomic, performant way.
Swift
476
star
17

swift-prelude

๐ŸŽถ A collection of types and functions that enhance the Swift language.
Swift
456
star
18

swift-perception

Observable tools, backported.
Swift
399
star
19

swift-validated

๐Ÿ›‚ A result type that accumulates multiple errors.
Swift
389
star
20

swift-url-routing

A bidirectional router with more type safety and less fuss.
Swift
321
star
21

swift-concurrency-extras

Useful, testable Swift concurrency.
Swift
267
star
22

swift-gen

๐ŸŽฑ Composable, transformable, controllable randomness.
Swift
261
star
23

swift-clocks

โฐ A few clocks that make working with Swift concurrency more testable and more versatile.
Swift
229
star
24

swift-enum-properties

๐Ÿค Struct and enum data access in harmony.
Swift
198
star
25

xctest-dynamic-overlay

Define XCTest assertion helpers directly in your application and library code.
Swift
197
star
26

swift-macro-testing

Magical testing tools for Swift macros.
Swift
192
star
27

syncups

A rebuild of Appleโ€™s โ€œScrumdingerโ€ application using modern, best practices for SwiftUI development.
Swift
153
star
28

composable-core-location

A library that bridges the Composable Architecture and Core Location.
Swift
100
star
29

vapor-routing

A bidirectional Vapor router with more type safety and less fuss.
Swift
84
star
30

swift-html-vapor

๐Ÿ’ง Vapor plugin for type-safe, transformable HTML views.
Swift
82
star
31

swift-playground-templates

๐Ÿซ A collection of helpful Xcode playground templates.
Makefile
80
star
32

pointfreeco-server

Point-Free server code.
39
star
33

swift-boundaries

๐Ÿฃ Functional core, imperative shell.
Swift
27
star
34

composable-core-motion

A library that bridges the Composable Architecture and Core Motion.
Swift
26
star
35

swift-quickcheck

๐Ÿ An implementation of QuickCheck in Swift.
Swift
24
star
36

swift-algebras

Algebraic laws bundled into concrete data types.
19
star
37

swift-either

For those times you want A or B!
Swift
19
star
38

swift-parser-printer

โ†”๏ธ Parsing and printing
Swift
14
star
39

swift-html-kitura

โ˜๏ธ Kitura plugin for type-safe, transformable HTML views.
Swift
13
star
40

homebrew-swift

Ruby
2
star
41

swift-bugs

2
star
42

Ccmark

Swift
1
star