{"id":24658444,"url":"https://github.com/swingdev/ios-inject-grail","last_synced_at":"2026-05-18T11:36:59.701Z","repository":{"id":50156887,"uuid":"339205783","full_name":"SwingDev/ios-inject-grail","owner":"SwingDev","description":"Easy dependency Injection framework for iOS and Mac","archived":false,"fork":false,"pushed_at":"2024-01-19T10:42:04.000Z","size":72,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-03-19T21:21:28.867Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/SwingDev.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}},"created_at":"2021-02-15T21:00:53.000Z","updated_at":"2024-04-15T15:26:28.220Z","dependencies_parsed_at":"2024-04-15T15:26:24.141Z","dependency_job_id":"5225a1c8-a85c-46d4-a2d5-1b1d8486dcb6","html_url":"https://github.com/SwingDev/ios-inject-grail","commit_stats":{"total_commits":20,"total_committers":6,"mean_commits":"3.3333333333333335","dds":0.4,"last_synced_commit":"9f27dfab3515c5db63566eae4a462556e9913183"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwingDev%2Fios-inject-grail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwingDev%2Fios-inject-grail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwingDev%2Fios-inject-grail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwingDev%2Fios-inject-grail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SwingDev","download_url":"https://codeload.github.com/SwingDev/ios-inject-grail/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244739963,"owners_count":20501992,"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":[],"created_at":"2025-01-26T01:35:29.477Z","updated_at":"2026-05-18T11:36:59.658Z","avatar_url":"https://github.com/SwingDev.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# InjectGrail\n\n[![Version](https://img.shields.io/cocoapods/v/InjectGrail.svg?style=flat)](https://cocoapods.org/pods/InjectGrail)\n[![License](https://img.shields.io/cocoapods/l/InjectGrail.svg?style=flat)](https://cocoapods.org/pods/InjectGrail)\n[![Platform](https://img.shields.io/cocoapods/p/InjectGrail.svg?style=flat)](https://cocoapods.org/pods/InjectGrail)\n\nThis project is fully functional, but it requires a lot of attention in several areas:\n\n- Documentation,\n- Example,\n- Tests,\n- Other process related stuff,\n- Comments and other readability improvements in generated code,\n- Readability improvements in Sourcery Template,\n- Basic framework info. Why, Inspirations, etc...\n\nIf you're willing to help then by all means chime in! We are open for PRs.\n\n# TL;DR\n\nThis\n\n```swift\nclass Context {\n     let networkProvider: NetworkProvider\n     let authProvider: AuthProvider\n     let localStorage: LocalStorage\n}\nlet context = Context(...)\n\nlet VC = MessagesViewController(networkProvider: context.networkProvider, authProvider: context.authProvider, localStorage: context.localStorage)\n//----------------------------------------------------------------\nclass MessagesViewController: UIViewController {\n    private let networkProvider: NetworkProvider\n    private let authProvider: AuthProvider\n    private let localStorage: LocalStorage\n    private let viewModel: MessagesViewModel\n\n    init(networkProvider: NetworkProvider, authProvider: AuthProvider,  localStorage: LocalStorage, ...) {\n       self.networkProvider = networkProvider\n       self.authProvider = authProvider\n       self.localStorage = localStorage\n       self.viewModel = MessagesViewModel(networkProvider: networkProvider, authProvider: authProvider, localStorage: localStorage, ...)\n    }\n}\n// ------------------------------------------------------------------\nclass MessagesViewModel {\n     let networkProvider: NetworkProvider\n     let authProvider: AuthProvider\n     let localStorage: LocalStorage\n\n    init(networkProvider: NetworkProvider, authProvider: AuthProvider,  localStorage: LocalStorage, ...) {\n       self.networkProvider = networkProvider\n       self.authProvider = authProvider\n       self.localStorage = localStorage\n    }\n\n    func checkIfLoggedIn() -\u003e Bool  {\n        self.authProvider.checkifLoggedIn()\n    }\n}\n```\n\nbecomes\n\n```swift\n\nclass Context: RootInjector {\n     let networkProvider: NetworkProvider\n     let authProvider: AuthProvider\n     let localStorage: LocalStorage\n}\nlet context = Context(...)\n\nlet VC: MessagesViewController = context.inject()\n//------------------------------------------------------\nprotocol MessagesViewControllerInjector {\n}\n\n @Needs\u003cMessagesViewControllerInjector\u003e\n @Injects\u003cMessagesViewModelInjector\u003e\n class MessagesViewController: UIViewController {\n    init(injector: MessagesViewControllerInjectorImpl) {\n       self.injector = injector\n       self.viewModel = inject()\n    }\n}\n// ------------------------------------------------------\n protocol MessagesViewModelInjector {\n    var networkProvider: NetworkProvider {get}\n    var authProvider: AuthProvider {get}\n    var localStorage: LocalStorage {get}\n }\n\n@Needs\u003cMessagesViewModelInjector\u003e\nclass MessagesViewModel {\n    func checkIfLoggedIn() -\u003e Bool  {\n        self.authProvider.checkifLoggedIn()\n    }\n}\n```\n\n- For each class you declare only dependencies needed by it. Not it's children.\n- You don't get big bag of dependencies that you have to carry to all classes in your project.\n- Dependencies are automatically pushed through hierarchy without touching parent classes definitions,\n- `init` of each class contains only those dependencies that are trully needed by it or it's children (wrapped in a simple struct),\n- Your classes can still be constructed manually,\n- `inject` functions take as arguments dependencies that have not been found in current class, but are required by children.\n- Not a single line of magic. You can Cmd+Click to see exact definitions. To achieve DI only protocols, structs and extensions are used.\n- Command Completion for everything.\n\n## Summary of terms:\n\n- `Injector` - specification of dependencies of a class\n- `Injectable` - Class that needs its dependencies to be injected (via `Injector` in init)\n- `InjectsXXX` - Must be implemented by parent class that wants to inject `XXX` injector.\n- `RootInjector` - Class or struct that implements this protocol will be automatically able to injects all `Injectors`. This is a top of injection tree. There must be exactly one class implementing this protocol.\n\n## Requirements\n\n## Installation\n\nInjectGrail is available through [CocoaPods](https://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod 'InjectGrail'\n```\n\n## Usage\n\n1.  `import InjectGrail`\n2.  For every class that needs to be `Injectable` instead o passing arguments directly to `init` create a protocol that will specify them and let it conform to `Injector` protocol.\n\n    For example, let's say we have a `MessagesViewModel` which we want to be injectable.\n\n    ```swift\n    class MessagesViewModel {\n       let networkManager: NetworkManager\n\n       init(networkManager: NetworkManager) {\n           self.networkManager = networkManager\n       }\n    }\n    ```\n\n    We need to create `MessagesViewModelInjector` - name doesn't matter. By convention we use `\u003cInjectableClassName\u003eInjector` and we let it conform to `Injector`\n\n    ```swift\n        protocol MessagesViewModelInjector: Injector {\n            var networkManager: NetworkManager {get}\n        }\n    ```\n\n3.  Add a new build script (before compilation):\n    ```bash\n    \"$PODS_ROOT/InjectGrail/Scripts/inject.sh\"\n    ```\n    If you ever encounter issues with underlying sourcery magic, you can pass extra arguments to it using `EXTRA` environment variable. For example to disable sourcery cache you can call `EXTRA=\"--disableCache\" \"$PODS_ROOT/InjectGrail/Scripts/inject.sh\"`\n4.  Add a class or struct that implements `RootInjector`. This will be your top most injector capable for injecting all other `Injectables`.\n    Injectables can be created manually as well.\n    ```swift\n    struct RootInjectorImpl: RootInjector {\n           let networkManager: NetworkManager\n           let messagesRepository: MessagesRepository\n           let authenticationManager: AuthenticationManager\n    }\n    ```\n5.  Compile. Injecting script will generate files `/InjectGrail/RootInjector.swift`, `/InjectGrail/Injectors.swift` and `/InjectGrail/Injectables.swift` in your project folder. Add them to project (and as an Output of buildstep added in previous steps).\n6.  For every class that needs to be `Injectable` let it implement `Injectable` and satisfy protocol requirements by creating field `injector` and `init(injector:...)`. Actual structs that can be used are created by the injection framework based on your `Injector`s definitions. For example for our `MessagesViewModel` we created protocol `MessagesViewModelInjector`, so injection framework created implementation in struct `MessagesViewModelInjectorImpl` (added `Impl`). We should use that.\n\n    ```swift\n    class MessagesViewModel: Injectable {\n        let injector: MessagesViewModelInjectorImpl\n\n        init(injector: MessagesViewModelInjectorImpl) {\n            self.injector = injector\n        }\n    }\n    ```\n\n    All properties from `MessagesViewModelInjector` can be used directly in `MessagesViewModel` via extension that was automatically created by `InjectGrail`. So in this case we can use `networkManager` directly.\n\n    ```swift\n    class MessagesViewModel: Injectable {\n       let injector: MessagesViewModelInjectorImpl\n\n       init(injector: MessagesViewModelInjectorImpl) {\n           self.injector = injector\n       }\n\n       func doSomeAction() {\n           self.networkManager.callBackend()\n       }\n    }\n    ```\n\n7.  For each `Injector` `InjectGrail` also creates protocol `Injects\u003cInjectorName\u003e` so in our case this would be `InjectsMessagesViewModelInjector`. Classes that are `Injectable` themselves and want to be able to inject to other `Injectables` can conform that protocol to create helper function `inject(...)`, that doesn injecting. `InjectGrail` automatically resolves dependencies between current class' `Injector` and target `Injector` and adds arguments to function `inject` for all that has not been found. Conforming to `Injects\u003cInjectorName\u003e` also adds all dependencies of the target to current injector `Impl`.\n\n    If we were to create `MessageRowViewModel` from `MessagesViewModel`. We would need to create `MessageRowViewModelInjector` and `let MessageRowViewModel implement Injectable`, like so:\n\n    ```swift\n    protocol MessageRowViewModelInjector: Injector {\n        var messagesRepository: MessagesRepository {get}\n        var messageIndex: Int {get}\n    }\n\n    class MessageRowViewModel: Injectable {\n       let injector: MessageRowViewModelInjectorImpl\n\n       init(injector: MessageRowViewModelInjectorImpl) {\n           self.injector = injector\n       }\n    }\n    ```\n\n    After running injection script we can make `MessagesViewModel` implement `InjectsMessageRowViewModelInjector` and after next run of script `MessagesViewModelInjectorImpl` would automatically get additional property `messagesRepository` - because it's provided by `RootInjector`, and `MessagesViewModel` would be extended with function `func inject(messageIndex: Int) -\u003e MessageRowViewModelInjector`, which it could use to create `MessageRowViewModel` like so:\n\n    ```swift\n    class MessagesViewModel: Injectable {\n       let injector: MessagesViewModelInjectorImpl\n\n       init(injector: MessagesViewModelInjectorImpl) {\n           self.injector = injector\n       }\n\n       func createRowViewModel() {\n         let rowViewModel = MessageRowViewModel(inject(messageIndex: 0))\n       }\n    }\n    ```\n\n    `Int`s and `String`s are never resolved during injection. Even if Injecting class also has it in its `Injector`.\n    Resolving migh be also disabled manually for field in Injector by adding Sourcery annotation:\n\n    ```swift\n    protocol MessageRowViewModelInjector: Injector {\n        var messagesRepository: MessagesRepository {get}\n        // sourcery: forceManual\n        var authenticationManager: AuthenticationManager {get}\n        var messageIndex: Int {get}\n    }\n    ```\n\n    In the example above `authenticationManager` will be always come from arguments to `inject` function of injecting classes.\n\n### Resolving logic\n\nWhen resolving dependency against parent Injector `InjectGrail` searches via type definition. If there are multiple properties of the same type, then it additionally matches by name. As mentioned above `Int`s and `String`s are never resolved.\n\n## Author\n\nŁukasz Kwoska, lukasz.kwoska@swing.dev\n\n## License\n\nInjectGrail is available under the MIT license. See the LICENSE file for more info.\n\n## Acknowledgement\n\n- This project couldn't exist without [Sourcery](https://github.com/krzysztofzablocki/Sourcery). It's the main component behind the scences.\n- [Annotation Inject](https://github.com/akane/AnnotationInject) - Thanks for showing me how easy it is to use sourcery from pod.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswingdev%2Fios-inject-grail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswingdev%2Fios-inject-grail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswingdev%2Fios-inject-grail/lists"}