{"id":32149289,"url":"https://github.com/rockname/sword","last_synced_at":"2025-10-21T09:53:09.536Z","repository":{"id":241427617,"uuid":"755908490","full_name":"rockname/sword","owner":"rockname","description":"🗡️ A compile time dependency injection library for Swift","archived":false,"fork":false,"pushed_at":"2025-04-06T07:21:14.000Z","size":3065,"stargazers_count":138,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-21T09:53:03.906Z","etag":null,"topics":["dependency-injection","swift"],"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/rockname.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2024-02-11T13:01:05.000Z","updated_at":"2025-10-04T14:25:26.000Z","dependencies_parsed_at":"2024-06-16T01:58:59.540Z","dependency_job_id":"a2b1f81e-b31b-4071-8457-fcc34a011ff9","html_url":"https://github.com/rockname/sword","commit_stats":null,"previous_names":["rockname/sword"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/rockname/sword","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockname%2Fsword","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockname%2Fsword/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockname%2Fsword/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockname%2Fsword/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rockname","download_url":"https://codeload.github.com/rockname/sword/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rockname%2Fsword/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280240305,"owners_count":26296527,"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","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["dependency-injection","swift"],"created_at":"2025-10-21T09:53:08.486Z","updated_at":"2025-10-21T09:53:09.521Z","avatar_url":"https://github.com/rockname.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eSword\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"assets/sword_logo.png\" width=200\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003eA compile time dependency injection library for Swift\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/rockname/sword/actions\"\u003e\u003cimg alt=\"build\" src=\"https://github.com/rockname/sword/workflows/test/badge.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"license\" src=\"https://img.shields.io/badge/license-MIT-black.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# Introduction\nSword is a compile time dependency injection library for Swift, inspired by [Dagger](https://dagger.dev/).\n\nAs you declare dependencies and specify how to satisfy them using [Swift Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/), Sword automatically generates dependency injection code at compile time. Sword walks through your code and validates dependency graphs, ensuring that every object's dependencies can be satisfied, so there are no runtime errors.\n\n# Installation\n\n## Xcode Package Dependency\n\nUse the following link to add Sword as a Package Dependency to an Xcode project:\n\n```\nhttps://github.com/rockname/sword\n```\n\n\u003e [!IMPORTANT]\n\u003e Do not add the `SwordCommand` executable to any targets.\n\u003e Ensure `None` is selected when asked to choose package products.\n\n## Swift Package Manager\n\nAdd the following to the package `dependencies` in your `Package.swift`:\n\n```swift\n.package(url: \"https://github.com/rockname/sword.git\", from: \"\u003cversion\u003e\")\n```\n\nThen, include \"Sword\" as a dependency for your target:\n\n```swift\n.target(\n  name: \"\u003ctarget\u003e\",\n  dependencies: [\n    .product(name: \"Sword\", package: \"sword\"),\n  ]\n),\n```\n\n# Setup\n\nSword provides a build tool plugin to generate dependency injection code.\n\nThe build tool plugin can be used in both Xcode projects and Swift Package projects.\n\n## Xcode projects\n\n\u003e [!NOTE]\n\u003e Requires installing via [Xcode Package Dependency](#xcode-package-dependency).\n\nAdd the `SwordBuildToolPlugin` to the `Run Build Tool Plug-ins` phase of the `Build Phases` for the target.\n\n\u003e [!TIP]\n\u003e When using the plugin for the first time, be sure to trust and enable\n\u003e it when prompted. If a macros build warning exists, select it to trust\n\u003e and enable the macros as well.\n\n## Swift Package projects\n\n\u003e [!NOTE]\n\u003e Requires installing via [Swift Package Manager](#swift-package-manager).\n\nAdd the plugin to the application root target as follows:\n\n```swift\n.target(\n    ...\n    plugins: [.plugin(name: \"SwordBuildToolPlugin\", package: \"Sword\")]\n),\n```\n\n## Xcode + Swift Package projects\n\nAdd the `SwordBuildToolPlugin` as mentioned above [Xcode projects](#xcode-projects).\n\nThen add a `.sword.yml` file into your Xcode project's root directory for Sword to read the file and generate your dependency graph considering local Swift Packages.\n\nFor example:\n\n```yml\nlocal_packages:\n  - path: PackageA\n    targets:\n      - DependencyA\n      - DependencyB\n  - path: PackageB\n    targets:\n      - DependencyC\n      - DependencyD\n```\n\n# Usage\n\nConsider an example SwiftUI app with the dependency graph from the following image.\n\n\u003cimg src=\"assets/example_login_1.png\" width=600\u003e\n\n## Component\n\nYou usually create a Sword dependency graph in your App struct (or root View) because you want an instance of the graph to be in memory as long as the app is running. In this way, the graph is attached to the app lifecycle.\n\nIn Sword, `@Component` is attached to the dependency graph. So you can call it `AppComponent`. You usually keep an instance of that component in your custom `App` struct as shown in the following:\n\n```swift\n// Definition of the App dependency graph\n@Component\nfinal class AppComponent {\n}\n\n// AppComponent lives in the App struct to share its lifecycle\n@main\nstruct MyApp: App {\n  let component = AppComponent()\n\n  var body: some Scene { ... }\n}\n```\n\nInstead of creating the dependencies a View requires in the `init`, you can get a dependency you want from the Component.\n\n```swift\nstruct LoginNavigation: View {\n  let component: AppComponent\n\n  var body: some View {\n    ...\n    LoginScreen(viewModel: component.loginViewModel)\n    ...\n  }\n}\n\nstruct LoginScreen: View {\n  let viewModel: LoginViewModel\n\n  var body: some View { ... }\n}\n```\n\n## Dependency\n\nSword needs to know required dependencies to provide the `LoginViewModel`. You can tell Sword how to initialize `LoginViewModel` using `@Dependency` / `@Injected` like following:\n\n```swift\n// You want Sword to provide an object of LoginViewModel from the AppComponent graph\n@Dependency(registeredTo: AppComponent.self)\nfinal class LoginViewModel {\n  private let userRepository: UserRepository\n\n  @Injected\n  init(userRepository: UserRepository) {\n    self.userRepository = userRepository\n  }\n}\n```\n\nLet's tell Sword how to provide the rest of the dependencies to build the graph:\n\n```swift\n@Dependency(registeredTo: AppComponent.self)\nfinal class UserRepository {\n  private let apiClient: APIClient\n\n  @Injected\n  init(apiClient: APIClient) {\n    self.apiClient = apiClient\n  }\n}\n\n@Dependency(registeredTo: AppComponent.self)\nfinal struct APIClient {\n  private let urlSession: URLSession\n\n  @Injected\n  init(urlSession: URLSession) {\n    self.urlSession = urlSession\n  }\n}\n```\n\n## Binding\n\nWhen you provide an interface for a dependency, use `boundTo` parameter on `@Dependency`.\n\n```swift\nprotocol APIClient { ... }\n\n// You want Sword to provide a DefaultAPIClient implementation for APIClient interface\n@Dependency(\n  registeredTo: AppComponent.self,\n  boundTo: APIClient.self\n)\nfinal struct DefaultAPIClient: APIClient {\n  ...\n}\n```\n\n## Module\n\nFor this example, `APIClient` has a dependency on `URLSession`. However, the way to create an instance of `URLSession` is different from what you've been doing until now. It's initializer is defined in the Foundation framework.\n\nApart from the `@Injected`, there's another way to tell Sword how to provide a required dependency: the information inside Sword modules. A Sword module is a struct that is attached with `@Module`. There, you can define dependencies with the `@Provider`.\n\n```swift\n// @Module informs Sword that this struct is a Sword Module registered to AppComponent\n@Module(registeredTo: AppComponent.self)\nstruct AppModule {\n  // @Provider tells Sword how to create the dependency.\n  @Provider\n  static func urlSession() -\u003e URLSession {\n    let configuration = URLSessionConfiguration.default\n    configuration.timeoutIntervalForRequest = 30\n    return URLSession(configuration: configuration)\n  }\n}\n```\n\nThis is how the Sword graph in the example looks right now:\n\n\u003cimg src=\"assets/example_login_2.png\" width=800\u003e\n\nThe entry point to the graph is `LoginScreen`. Because `LoginScreen` injects `LoginViewModel`, Sword builds a graph that knows how to provide an instance of `LoginViewModel`, and recursively, of its dependencies. Sword knows how to do this because of the `@Injected` on the dependencies' initializer.\n\n## Scope\n\nYou can use `Scope` to limit the lifetime of an object to the lifetime of its component. This means that the same instance of a dependency is used every time that type needs to be provided.\n\nTo have a unique instance of a `UserRepository` when you ask for the repository in `AppComponent`, pass `.single` to the `scopedWith` parameter on `@Dependency`.\n\n```swift\n@Dependency(\n  registeredTo: AppComponent.self,\n  scopedWith: .single\n)\nfinal class UserRepository { ... }\n```\n\nYou can also use the `scopedWith` parameter in `@Provider`.\n\n```swift\n@Module(registeredTo: AppComponent.self)\nstruct AppModule {\n  @Provider(scopedWith: .single)\n  static func urlSession() -\u003e URLSession { ... }\n}\n```\n\n## Subcomponent\n\nIf your login flow consists of multiple views, you would want to reuse the same instance of `LoginViewModel` in all views. But you should not use `signle` scope in `AppComponent` for the following reasons:\n\n1. The instance of `LoginViewModel` would persist in memory after the login flow has finished.\n\n2. You want a different instance of `LoginViewModel` for each login flow. For example, if the user logs out, you want a different instance of `LoginViewModel`, rather than the same instance as when the user logged in for the first time.\n\nTo scope `LoginViewModel` to the lifecycle of login flow, you need to create a new component for the login flow.\n\nThe new component must be able to access the objects from `AppComponent` because `LoginViewModel` depends on `UserRepository`. The way to tell Sword that you want a new component to use part of another component is with Sword `Subcomponent`. The new component must be a subcomponent of the component containing shared resources.\n\nIn the example, you must define `LoginComponent` as a subcomponent of `AppComponent` like following:\n\n```swift\n// You tell Sword that LoginComponent is a subcomponent of AppComponent\n@Subcomponent(of: AppComponent.self)\nfinal class LoginComponent {\n}\n```\n\nA factory method `func makeLoginComponent() -\u003e LoginComponent` will be generated in `AppComponent`.\nYou call this method when starting the login flow.\n\n```swift\n@main\nstruct MyApp: App {\n  let component = AppComponent()\n\n  var body: some Scene {\n    ...\n    LoginNavigation(component: component.makeLoginComponent())\n    ...\n  }\n}\n\nstruct LoginNavigation: View {\n  let component: LoginComponent\n\n  var body: some View {\n    ...\n    LoginScreen(viewModel: component.loginViewModel)\n    ...\n  }\n}\n```\n\nThen, as you set `LoginComponent.self` to `registeredTo` and `.single` to `scopedWith` on `@Dependency` of `LoginViewModel`, the instance of `LoginViewModel` would be unique in each login flow.\n\n```swift\n@Dependency(\n  registeredTo: LoginComponent.self,\n  scopedWith: .single\n)\nfinal class LoginViewModel { ... }\n```\n\nHere is how the Sword graph looks with the new subcomponent. The classes with a white dot (`UserRepository`, `URLSession`, and `LoginViewModel`) are the ones that have a unique instance scoped to their respective components.\n\n\u003cimg src=\"assets/example_login_3.png\" width=800\u003e\n\n## Component Arguments\n\nYou can pass some arguments to a component as a dependency.\n\nFor example, you can inject environment variables, `EnvVars`, to `AppComponent` like following:\n\n```swift\nstruct EnvVars {\n  let baseURL: URL\n}\n\n@Component(arguments: EnvVars.self)\nfinal class AppComponent {\n}\n```\n\nThen, the `@Component` macro generates an initializer receiving `EnvVars` as a parameter.\n\n```swift\nlet component = AppComponent(\n  envVars: EnvVars(baseURL: URL(string: \"https://example.com\")!)\n)\n```\n\nNow you can resolve an `EnvVars` dependency via `AppComponent`.\n\n```swift\n@Dependency(\n  registeredTo: AppComponent.self,\n  boundTo: APIClient.self,\n  scopedWith: .single\n)\nfinal class DefaultAPIClient: APIClient {\n  private let baseURL: URL\n\n  @Injected\n  init(envVars: EnvVars) {\n    self.baseURL = envVars.baseURL\n  }\n}\n```\n\n## Assisted Injection\n\nAssisted injection is a dependency injection (DI) pattern that is used to construct an object where some parameters may be provided by the DI framework and others must be passed in at creation time (a.k.a “assisted”) by the user.\n\nTo use Sword’s assisted injection, annotate any assisted parameters with `@Assisted`, as shown below:\n\n```swift\n@Dependency(registeredTo: AppComponent.self)\nclass UserDetailViewModel {\n  ...\n\n  @Injected\n  init(\n    @Assisted userID: User.ID,\n    userRepository: UserRepository\n  ) {\n    self.userID = userID\n    self.userRepository = userRepository\n  }\n}\n```\n\nThen you can pass the assisted parameter when using the dependency as shown below.\n\n```swift\nstruct UserNavigation: View {\n  let component: AppComponent\n\n  var body: some View {\n    ...\n    UserDetailScreen(viewModel: component.userDetailViewModel(userID: userID))\n    ...\n  }\n}\n```\n\n# Features\n\n| Feature | Support Status |\n| --- | --- |\n| Subcomponent | ✅ Supported |\n| Component Arguments | ✅ Supported |\n| Single Scope | ✅ Supported |\n| Weak Reference Scope | ✅ Supported |\n| Assisted Injection | ✅ Supported |\n| Missing Dependency Error | ✅ Supported |\n| Duplicate Dependency Error | ✅ Supported |\n| Cycle Dependency Error | ✅ Supported |\n\n# License\nThis library is released under the MIT license. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frockname%2Fsword","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frockname%2Fsword","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frockname%2Fsword/lists"}