A statically typed dependency injector for Swift.
DIKit provides interfaces to express dependency graph. A code generator named dikitgen finds implementations of the interfaces, and generate codes which satisfies dependency graph.
The main parts of DIKit are injectable types and provider methods, and both of them are to declare dependencies of types.
Injectable types are types that conform to Injectable protocol.
publicprotocolInjectable{associatedtypeDependencyinit(dependency:Dependency)}A conformer of Injectable protocol must have associated type Dependency as a struct. You declare dependencies of the Injectable conformer as stored properties of Dependency type. For example, suppose we have ProfileViewController class, and its dependencies are User, APIClient and Database. Following example code illustrates how to declare dependencies by conforming Injectable protocol.
finalclassProfileViewController:Injectable{structDependency{letuser:UserletapiClient:APIClientletdatabase:Database}init(dependency:Dependency){...}}Provider methods are methods of inheritor of Resolver protocol, which is a marker protocol for code generation.
publicprotocolResolver{}Provider methods declares that which non-injectable types can be instantiated automatically. In the example above, APIClient and Database are non-injectable type, but they can be provided in the same ways in most cases. In this situation, define provider methods for the types in an inheritor of Resolver protocol, so that instances of the types are provided automatically.
protocolAppResolver:Resolver{func provideAPIClient()->APIClientfunc provideDatabase()->Database}In short, we have following situation so far:
- Dependencies of
ProfileViewControllerareUser,APIClientandDatabase. - Instances of
APIClientandDatabaseare provided automatically. - An instance of
Usermust be provided manually to instantiateProfileViewController.
dikitgen generates following code for the declarations:
extensionAppResolver{func resolveAPIClient()->APIClient{returnprovideAPIClient()}func resolveDatabase()->Database{returnprovideDatabase()}func resolveViewController(user:User)->ProfileViewController{letapiClient=resolveAPIClient()letdatabase=resolveDatabase()returnProfileViewController(dependency:.init(user: User, apiClient: apiClient, database: Database))}}To use generated code, you have to implement a concrete type of AppResolver.
finalclassAppResolverImpl:AppResolver{letapiClient:APIClient=...letdatabase:Database=...func provideAPIClient(){return apiClient
}func provideDatabase(){return database
}}Since AppResolver is a protocol, all implementations of provider methods are checked at compile time. If you would like to create mock version of AppResolver for unit testing, define another concrete type of AppResolver. It can be used the same as AppResolverImpl.
Now, you can instantiate ProfileViewController like below:
letappResolver=AppResolverImpl()letuser:User=...letviewController= appResolver.resolveViewController(user: user)- Code generator: Swift 4.1+ / Xcode 9.4+
- Runtime library: macOS 10.11+ / iOS 9.0+ / watchOS 2.0+ / tvOS 9.0+
Install code generator dikitgen first.
mint install ishkawa/DIKit dikitgengit clone https://github.com/ishkawa/DIKit.git
cd DIKit
make installThen, integrate DIKit.framework to your project. There are some option to install DIKit.framework.
- Manual: Clone this repository and add
DIKit.xcodeprojto your project. - Carthage: Add a line
github "ishkawa/DIKIt"to your Cartfile and runcarthage update.
Optionally, insert shell script running dikitgen to early part of build phases.
if which dikitgen >/dev/null;then
dikitgen ${SRCROOT}/YOUR_PROJECT >${SRCROOT}/YOUR_PROJECT/AppResolver.generated.swift
elseecho"warning: dikitgen not installed, download from https://github.com/ishkawa/DIKit"fi