{"id":21512022,"url":"https://github.com/strvcom/ios-dependency-injection","last_synced_at":"2026-04-24T11:12:57.068Z","repository":{"id":42437922,"uuid":"350776270","full_name":"strvcom/ios-dependency-injection","owner":"strvcom","description":"Dependency injection library","archived":false,"fork":false,"pushed_at":"2026-04-23T11:59:30.000Z","size":282,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-04-23T13:33:50.925Z","etag":null,"topics":["ios","ios-library"],"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/strvcom.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-03-23T16:09:49.000Z","updated_at":"2026-03-06T12:36:36.000Z","dependencies_parsed_at":"2025-02-12T10:36:26.721Z","dependency_job_id":"f6b0aa82-320d-4088-9b4e-a2a29b77e9c8","html_url":"https://github.com/strvcom/ios-dependency-injection","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/strvcom/ios-dependency-injection","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fios-dependency-injection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fios-dependency-injection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fios-dependency-injection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fios-dependency-injection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strvcom","download_url":"https://codeload.github.com/strvcom/ios-dependency-injection/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fios-dependency-injection/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32220531,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T10:26:35.452Z","status":"ssl_error","status_checked_at":"2026-04-24T10:25:27.643Z","response_time":64,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["ios","ios-library"],"created_at":"2024-11-23T22:25:36.553Z","updated_at":"2026-04-24T11:12:57.057Z","avatar_url":"https://github.com/strvcom.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dependency Injection\n\n[![build](https://github.com/strvcom/ios-dependency-injection/actions/workflows/integrations.yml/badge.svg)](https://github.com/strvcom/ios-dependency-injection/actions/workflows/integrations.yml/badge.svg)\n[![Coverage](https://img.shields.io/badge/Coverage-100%25-darkgreen?style=flat-square)](https://img.shields.io/badge/Coverage-100%25-darkgreen?style=flat-square)\n[![Platforms](https://img.shields.io/badge/Platforms-iOS_iPadOS_macOS_tvOS_watchOS-lightgrey?style=flat-square)](https://img.shields.io/badge/Platforms-iOS_iPadOS_macOS_tvOS_watchOS-lightgrey?style=flat-square)\n[![Swift](https://img.shields.io/badge/Swift-5.3_5.4_5.5-blue?style=flat-square)](https://img.shields.io/badge/Swift-5.3_5.4_5.5-blue?style=flat-square)\n\nThe lightweight library for dependency injection in Swift. For detailed API documentation, see the generated DocC documentation in Xcode (Product → Build Documentation) or browse the source code.\n\n## Requirements\n\n- iOS/iPadOS 13.0+, macOS 10.15+, watchOS 6.0+, tvOS 13.0+\n- Xcode 11+\n- Swift 5.3+\n\n## Installation\n\nYou can install the library with [Swift Package Manager](https://swift.org/package-manager/). Once you have your Swift package set up, adding Dependency Injection as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/strvcom/ios-dependency-injection.git\", .upToNextMajor(from: \"1.0.0\"))\n]\n```\n\n## Dependency Injection\n\nIf you are new to the concept of Dependency Injection, you can check [Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection) for a general introduction and brief overview.\n\n## Usage\n\nA container is a key component of Dependency Injection. A container manages dependencies of your codebase. First, you register your dependencies within the container identified by either their types, or protocols or classes they conform to or inherit from respectively. Then, you use the container to get (i.e. resolve) instances of the registered dependencies.\n\nThe library provides two container types:\n- **`Container`** - Synchronous container for traditional dependency injection\n- **`AsyncContainer`** - Asynchronous, actor-based container for Swift concurrency (thread-safe)\n\nOther terminology that might be useful:\n\n- **[Factory](Sources/Protocols/Registration/DependencyRegistering.swift)** - A function or closure instantiating a dependency\n- **[Scope](Sources/Models/DependencyScope.swift)** - A scope of a registered dependency can be either `new` or `shared`. When a dependency is registered with `new` scope, a new instance of the dependency is created each time the dependency is resolved from the container. When a dependency is registered with `shared` scope, a new instance of the dependency is created only the first time it is resolved from the container. The created instance is cached and it is returned for all upcoming resolution requests, i.e. it is a singleton\n- **Registration with arguments** - All dependencies must be initialized and their initializers often have parameters. Typically, the objects that are passed as the input parameters are resolved from the same container. But you might want to have a registered dependency which requires a parameter in its initializer that can't be registered in the container. In such case, you register the dependency with variable arguments (1, 2, or 3 arguments supported) and you specify values of the arguments when the dependency is being resolved; the values are passed as input parameters to the dependency factory.\n\n### Registration\n\nA dependency is registered with the `register` method of the container. A dependency is registered with a type that is either its own type, or a protocol or a class that the dependency conforms to or inherits from respectively. Next, a scope of registration must be specified (see the terminology above). Finally, a factory closure or function that returns an instance of the dependency must be provided.\n\nIt can look like this:\n```swift\nlet container = Container()\ncontainer.register(type: Dependency.self, in: .shared) { container in\n  Dependency(\n    manager: container.resolve(type: Manager.self)\n  )\n}\n```\nWe can also use the fact that the type is by default inferred from the factory return type and `shared` is the default scope so we can simplify the above snippet into this:\n```swift\nlet container = Container()\ncontainer.register { container in\n  Dependency(\n    manager: container.resolve(type: Manager.self)\n  )\n}\n```\nMoreover, if we want to register a shared dependency that has no sub-dependencies from the container we can use an overloaded registration method with an autoclosure like this:\n```swift\nlet container = Container()\ncontainer.register(dependency: SimpleDependency())\n```\n\n### Registration with an argument\n\nSee the terminology above to understand what we mean by the registration with an argument.\n\n_DISCUSSION: The registration with an argument doesn't have any scope parameter and it is for a reason. The container should always return a new instance for dependencies with arguments as the behaviour for resolving shared instances with arguments is undefined. Should the argument conform to `Equatable` to compare the arguments to tell whether a shared instance with a given argument was already resolved? Shared instances are typically not dependent on variable input parameters by definition._\n\nLet's assume that our dependency from above needs also an integer that is determined right before the dependency is supposed to be resolved from the container. There is no point in registering the integer as a dependency in the container, moreover, we typically don't even want to register simple types like integers. For such case, we have the registration with an argument:\n```swift\nlet container = Container()\ncontainer.register { container, number in\n  Dependency(\n    integer: number,\n    manager: container.resolve(type: Manager.self)\n  )\n}\n```\n\nArgument matching is based on the compile-time type of each argument. That means `ConcreteType` and `any SomeProtocol` are different registrations even if the concrete value conforms to the protocol:\n```swift\nlet container = Container()\ncontainer.register { _, dependency: any DIProtocol in\n  DependencyWithProtocolParameter(subDependency: dependency)\n}\n\nlet concrete = StructureDependency(property1: \"42\")\nlet existential: any DIProtocol = concrete\n\nlet service: DependencyWithProtocolParameter = container.resolve(arguments: existential) // works\n// container.resolve(arguments: concrete) throws because StructureDependency != any DIProtocol\n```\n\n### Autoregistration\n\nLet's have look at an example from above:\n```swift\nlet container = Container()\ncontainer.register { container in\n  Dependency(\n    manager: container.resolve(type: Manager.self)\n  )\n}\n```\nIn the factory closure, we typically just call the dependency initializer and we resolve its input parameters from the container. You can get rid of this duplicated boiler-plate by using `autoregister` method where you specify just the initializer that should be used to initialize the dependency, instead of writing the same factories over and over again. The above example then looks like this:\n```swift\nlet container = Container()\ncontainer.autoregister(in: .shared, initializer: Dependency.init)\n```\nSimilarly, we can use autoregistration with an argument and replace this:\n```swift\nlet container = Container()\ncontainer.register { container, number in\n  Dependency(\n    integer: number,\n    manager: container.resolve(type: Manager.self)\n  )\n}\n```\nWith the following:\n```swift\nlet container = Container()\ncontainer.autoregister(argument: Int.self, initializer: Dependency.init)\n```\n\n### Resolution\n\nDependency resolution is very straightforward. You can use either the container's `tryResolve` method that throws an error when something goes wrong, or simply `resolve` which returns the resolved non-optional dependency but if anything goes wrong, your app will crash.\n\nYou can resolve a registered dependency like this:\n```swift\nlet container = Container()\ncontainer.register { container in\n  Dependency(\n    manager: container.resolve(type: Manager.self)\n  )\n}\n\nlet dependency = container.resolve(type: Dependency.self)\nlet dependency2: Dependency = container.resolve()\n```\n\nOr a dependency registered with arguments like this:\n```swift\nlet container = Container()\ncontainer.register { container, number in\n  Dependency(\n    integer: number,\n    manager: container.resolve(type: Manager.self)\n  )\n}\n\nlet dependency = container.resolve(type: Dependency.self, argument: 42)\nlet dependency2: Dependency = container.resolve(argument: 42)\n```\n\nThe library also supports 2 and 3 arguments:\n```swift\ncontainer.register { container, userId, apiKey in\n  AuthenticatedService(userId: userId, apiKey: apiKey)\n}\n\nlet service: AuthenticatedService = container.resolve(argument1: \"user123\", argument2: \"key456\")\n```\n\n### Property wrappers\n\nThe package contains also two convenient property wrappers `@Injected` and `@LazyInjected`. As long as you are fine with using the `Container.shared` or any other static container instance, you can use the following syntactic sugar to resolve dependencies:\n```swift\nclass Singletons {\n  static let container = Container()\n  \n  static func configure() {\n    container.autoregister(in: .shared, initializer: Dependency.init)\n  }\n}\n\nclass Object {\n  @Injected(from: Singletons.container) var dependency: Dependency\n}\n```\nOr if you use the `Container.shared` singleton, then you can write simply:\n```swift\nclass Object {\n  @Injected var dependency: Dependency\n}\n```\nWhen using the `@Injected` property wrapper, the dependency is resolved right in the moment when the property is instantiated. If you prefer to resolve the dependency only when it is accessed for the first time, you should rather use `@LazyInjected`:\n```swift\nlet container = Container()\ncontainer.autoregister(in: .shared, initializer: Dependency.init)\n\nclass Object {\n  @LazyInjected(from: container) var dependency: Dependency\n  // Resolve from `Container.shared`\n  @LazyInjected var dependency2: Dependency\n  \n  func doStuff() {\n    dependency.doStuff()\n    dependency2.doStuff()\n  }\n}\n```\nIn the example above the dependencies aren't resolved immediately when an instance of `Object` is initialized but only when the `doStuff` method is called for the first time.\n\n### AsyncContainer\n\nFor Swift concurrency, use `AsyncContainer` which is an actor-based container providing thread-safe dependency injection:\n\n```swift\nlet container = AsyncContainer.shared\n\n// Register dependencies (async)\nawait container.register { resolver in\n  await MyService(\n    apiClient: await resolver.resolve(type: APIClient.self)\n  )\n}\n\n// Resolve dependencies (async)\nlet service: MyService = await container.resolve()\n```\n\nAll `AsyncContainer` operations are `async` and thread-safe. Use it when working with Swift concurrency or when thread safety is required.\n\n**Note:** Property wrappers (`@Injected` and `@LazyInjected`) work only with `Container`, not `AsyncContainer`.\n\n## Roadmap\n\n- [x] Register and resolve a shared instance\n- [x] Register and resolve a new instance\n- [x] Register an instance with an identifier\n- [x] Register an instance with an argument\n- [x] Convenient property wrapper\n- [x] Autoregister\n- [x] SPM package\n- [x] Register an instance with multiple arguments\n- [x] AsyncContainer for Swift concurrency (thread-safe)\n- [ ] Container hierarchy\n- [ ] Detect circular dependencies\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrvcom%2Fios-dependency-injection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrvcom%2Fios-dependency-injection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrvcom%2Fios-dependency-injection/lists"}