Deli is an easy-to-use Dependency Injection Container that creates DI containers

Overview

Deli

Swift Version License CI Status Jazzy Platform

Deli is an easy-to-use Dependency Injection Container that creates DI containers with all required registrations and corresponding factories.

Language Switch: English, 한국어.

Table of Contents

Overview

Wanna spaghetti? or not. As your project grows, will experience a complex. We can write the wrong code by mistake.

In Spring framework provides automatic registration using some code rules and throws the wrong Dependency Graph before running. I wanted these features to be in Swift.

Getting Started

Simple setup for the automated configuration files, deli.yml.

If the configuration file does not exist, find the build target for a unique project in the current folders automatically. It works the same even if no scheme, target and output field is specified.

target:
  - MyProject

config:
  MyProject:
    project: MyProject
    scheme: MyScheme
    include:
      - Include files...
    exclude:
      - Exclude files...
    className: DeilFactory
    output: Sources/DeliFactory.swift
    resolve:
      output: Deli.resolved
      generate: true
    dependencies:
      - path: Resolved files...
        imports: UIKit
    accessControl: public

You’ll have to make your scheme Shared. To do this Manage Schemes and check the Shared areas:

shared-build-scheme

Alternatively, you can specify target instead of scheme. In this case, Deli will find the Build Target.

Then build with the provided binaries.

$ deli build

Dependency Graph is configured through source code analysis. It is saved as the file you specified earlier.

File contents as below:

//
//  DeliFactory.swift
//  Auto generated code.
//

import Deli

final class DeliFactory: ModuleFactory {
    override func load(context: AppContextType) {
        ...
    }
}

Add the generated file to the project and call it from the app's launch point.

drag-and-drop

AppDelegate.swift:

import UIKit
import Deli

class AppDelegate {
    
    var window: UIWindow?

    let context = AppContext.load([
        DeliFactory.self
    ])

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        return true
    }
}

Build Phases

Integrate Deli into an Xcode scheme to get warnings and errors displayed in the IDE. Just add a new "Run Script Phase" with:

if which deli >/dev/null; then
  deli build
else
  echo "error: Deli not installed, download from https://github.com/kawoou/Deli"
fi

Build Phase

Alternatively, if you've installed Deli via CocoaPods the script should look like this:

"${PODS_ROOT}/DeliBinary/deli" build

Features

1. Component

The class, struct, and protocol can extend the Component protocol and will be registered automatically in the DI container.

Component can be used as below:

protocol UserService {
    func login(id: String, password: String) -> User?
    func logout()
}

class UserServiceImpl: UserService, Component {
    func login(id: String, password: String) -> User? {
        ...
    }
    func logout() {
        ...
    }

    init() {}
}

If the above code is written, you can use the UserService or UserServiceImpl type to load the dependency instance.

2. Autowired

The Autowired protocol is registered automatically, same as Component protocol. A difference, you can load the required dependencies from DI container.

Autowired can be used as below:

class LoginViewModel: Autowired {
    let userService: UserService

    required init(_ userService: UserService) {
        self.userService = userService 
    }
}

Easy right? So let's look at the code below.

protocol Book {
    var name: String { get }
    var author: String { get }
    var category: String { get }
}

class Novel: Book {
    var qualifier: String {
        return "Novel"
    }

    var name: String {
        return ""
    }
    
    var author: String {
        return ""
    }
    
    var category: String {
        return "Novel"
    }
}

class HarryPotter: Novel, Component {
    override var name: String {
        return "Harry Potter"
    }
    
    override var author: String {
        return "J. K. Rowling"
    }
}

class TroisiemeHumanite: Novel, Component {
    override var name: String {
        return "Troisième humanité"
    }
    
    override var author: String {
        return "Bernard Werber"
    }
}

This code arranged the books through inheritance. You can get all of Book instances like below:

class LibraryService: Autowired {
    let books: [Book]

    required init(_ books: [Book]) {
        self.books = books
    }
}

Furthermore, What should do to get the books with the "Novel" qualifier? In Deli, can be constructor injection in the below:

class LibraryService: Autowired {
    let books: [Book]

    required init(Novel books: [Book]) {
        self.books = books
    }
}

3. LazyAutowired

If we can remove whole Circular Dependency cases, the world will be better than before, but it cannot be ruled completely. A simple way to solve this problem is to initialize one of the dependency lazily.

Let's try LazyAutowired protocol:

class UserService: Autowired {
    let messageService: MessageService

    required init(_ messageService: MessageService) {
        self.messageService = messageService
    }
}
class FriendService: Autowired {
    let userService: UserService

    required init(_ userService: UserService) {
        self.userService = userService
    }
}
class MessageService: Autowired {
    let friendService: FriendService

    required init(_ friendService: FriendService) {
        self.friendService = friendService
    }
}

If you try to inject a MessageService, Circular Dependency will occurred.

$ deli validate

Error: The circular dependency exists. (MessageService -> FriendService -> UserService -> MessageService)

What if UserService extends LazyAutowired?

class UserService: LazyAutowired {
    let messageService: MessageService!

    func inject(_ messageService: MessageService) {
        self.messageService = messageService
    }

    required init() {}
}

The cycle was broken and the issue was resolved! After MessageService instance successfully created, dependencies can be injected via inject() that UserService needed.

In addition, LazyAutowired can be specified qualifier like Autowired. Below code injects a UserService instance with the "facebook" qualifier specified:

class FacebookViewModel: LazyAutowired {
    let userService: UserService!

    func inject(facebook userService: UserService) {
        self.userService = userService
    }

    required init() {}
}

4. Configuration

The Configuration protocol makes the user can register Resolver directly.

Let's look at the code:

class UserConfiguration: Configuration {
    let networkManager = Config(NetworkManager.self, ConfigurationManager.self) { configurationManager in
        let privateKey = "1234QwEr!@#$"
        return configurationManager.make(privateKey: privateKey)
    }

    init() {}
}

You can see privateKey is passed to ConfigurationManager on NetworkManager creation.

This NetworkManager instance is registered in DI container, and it will be managed as singleton. (However, instance behavior can be changed by updating scope argument.)

5. Inject

As written, Autowired is registered in DI container. But you may want to use without registration. That's an Inject.

class LoginView: Inject {
    let viewModel = Inject(LoginViewModel.self)

    init() {}
}

class NovelBookView: Inject {
    let novels: [Book] = Inject([Book].self, qualifier: "Novel")

    init() {}
}

6. Factory

In the front-end, often dynamically generating a model using the user's data. Let's take an example.

You must implement a friend list. When you select a cell from friends list, you need to present modal view of friend's information. In this case, The friend data must be passed in the Info Modal.

This happens very often, Factory will help them.

Let's try AutowiredFactory protocol:

class FriendPayload: Payload {
    let userID: String
    let cachedName: String
    
    required init(with argument: (userID: String, cachedName: String)) {
        userID = argument.userID
        cachedName = argument.cachedName
    }
}

class FriendInfoViewModel: AutowiredFactory {
    let accountService: AccountService
    
    let userID: String
    var name: String
    
    required init(_ accountService: AccountService, payload: FriendPayload) {
        self.accountService = accountService
        self.userID = payload.userID
        self.name = payload.cachedName
    }
}

To pass a user-argument, you must implement a Payload protocol. (Naturally, factories work by prototype scope)

Implemented FriendInfoViewModel can be used as below:

class FriendListViewModel: Autowired {
    let friendService: FriendService
    
    func generateInfo(by id: String) -> FriendInfoViewModel? {
        guard let friend = friendService.getFriend(by: id) else { return nil }
        
        return Inject(
            FriendInfoViewModel.self,
            with: (
                userID: friend.id,
                cachedName: friend.name
            )
        )
    }
    
    required init(_ friendService: FriendService) {
        self.friendService = friendService
    }
}

Next LazyAutowiredFactory protocol:

class FriendInfoViewModel: LazyAutowiredFactory {
    var accountService: AccountService!
    
    func inject(facebook accountService: AccountService) {
        self.accountService = accountService
    }
    
    required init(payload: TestPayload) {
        ...
    }
}

The difference between an AutowiredFactory and a LazyAutowiredFactory is that it is lazy injected with the relationship between Autowired and LazyAutowired. However, payload injects by the constructor because passed by the user.

7. ModuleFactory

When injecting the dependency, required blueprint. As above, This blueprint is generated at build(ex. DeliFactory). When calling AppContext#load(), load container of generated class that inherited ModuleFactory.

Deli supports Multi-Container. Can be used ModuleFactory as below.

7.1. Multi-Container

When calling AppContext#load(), also load the ModuleFactory in the module.

Can specify LoadPriority in this situation. This is the order for selecting the container to be used in dependency injection.

Priority are normal(500) defaultly. Container's order for selecting as below:

  1. High priority first.
AppContext.shared.load([
    OtherModule.DeliFactory.self,
    DeliFactory.self
])
  1. If priority is same, In the loaded order.
AppContext.shared
    .load(DeliFactory())
    .load(OtherModule.DeliFactory(), priority: .high)

7.2. Unit Test

Priority loading that same as 7.1 used be Unit Test, too.

import Quick
import Nimble

@testable import MyApp

class UserTests: QuickSpec {
    override func spec() {
        super.spec()

        let testModule: ModuleFactory!
        testModule.register(UserService.self) { MockUserService() }

        let appContext = AppContext.shared
        beforeEach {
            appContext.load(testModule, priority: .high)
        }
        afterEach {
            appContext.unload(testModule)
        }
        
        ...
    }
}

An example of a test code is Deli.xcodeproj.

8. Struct

Support for Struct has been added since version 0.7.0.

The basic behavior is the same as Class, but one difference is that cannot use weak Scope.

Below is an example of Moya's plugin implementation.

struct AuthPlugin: PluginType, LazyAutowired {

    var scope: Scope = .weak

    private let authService: AuthService!

    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
        var request = request

        if let authToken = authService.authToken {
            request.addValue(authToken.accessToken, forHTTPHeaderField: "Authorization")
            request.addValue(authToken.refreshToken, forHTTPHeaderField: "Refresh-Token")
        }

        return request
    }

    mutating func inject(_ authService: AuthService) {
        self.authService = authService
    }

    init() {}
}

9. Configuration Property

It's often profit to use different configuration values depending on the running environment. For example, you can specific that save the file log at development build and not save the file log at the Release build.

application-dev.yml:

logger:
    storage: file

server:
    url: https://dev.example.com/api
    isDebug: false

application-prod.yml:

logger:
    storage: default

server:
    url: https://www.example.com/api
    isDebug: true

9.1. Usage

Two ways solution to use the Configuration Property created above.

  1. Change deli.yml.
  2. Modify the build script

Change the configuration file as below:

target:
- MyApp

config:
  MyApp:
    - project: MyApp
    - properties:
      - Configurations/Common/*.yml
      - Configurations/application-dev.yml

Build script can do this:

deli build \
  --property "Configurations/Common/*.yml" \
  --property "Configurations/application-dev.yml"

If the same configuration information, it's overwritten with the last specified information.

9.2. Group Value

You can use ConfigProperty to safe retrieve the specified value in the configuration file.

struct ServerConfig: ConfigProperty {
    let target: String = "server"

    let url: String
    let isDebug: Bool
}

When implementing the model as above, ServerConfig is registered in IoC Container.

One thing to keep in mind when defining the model, need to set the target value. This property represents the path to retrieve in the configuration file using JSONPath style.

If you do not have the required configuration values at build time, will occurred a compile error.

final class NetworkManager: Autowired {
    let info: ServerConfig

    required init(_ config: ServerConfig) {
        info = config
    }
}

9.3. Single Value

When get a bundle value as above, implement the ConfigProperty protocol. So how to get a single value? You can use the InjectProperty.

final class NetworkManager: Inject {
    let serverUrl = InjectProperty("server.url")
}

InjectProperty is similar to ConfigProperty. It checks the configuration value at build time and inject data as String type.

If you want to retrieve configuration value optionally without validation, this is not a proper way.

In this case, recommend using the AppContext#getProperty() method.

final class NetworkManager {
    let serverUrl = AppContext.getProperty("server.url", type: String.self) ?? "https://wtf.example.com"
}

9.4. Qualifier by Property

To enhance usability of configuration property, Deli provides a way of injection using qualifier as configuration value.

There are two ways to use it. let's look first that constructor injection like Autowired.

As mentioned in the Autowired paragraph, you can not use . for parts that specify qualifier. Unfortunately, swift do not has an annotation-like features. So I implemented to use comment as an alternative.

How it works:

final class UserService: Autowired {
    required init(_/*logger.storage*/ logger: Logger) {
    }
}

When using the Inject method:

final class UserService: Inject {
    func getLogger() -> Logger {
        return Inject(Logger.self, qualifierBy: "logger.storage")
    }
}

10. PropertyWrapper

For easier use, supports the @propertyWrapper added in Swift 5.1.

There are two main features to be supported: dependency injection and Configuration Property.

10.1. Dependency

There are @Dependency and @DependencyArray for injection of dependencies.

class Library {
    @Dependency(qualifier "logger.storage")
    var logger: Logger

    @DependencyArray(qualifier: "novel")
    var novels: [Book]
}

10.2. PropertyValue

@PropertyValue is the same as Configuration Property and the usage as below:

final class NetworkManager: Inject {
    @PropertyValue("server.url")
    let serverUrl: String
}

Installation

Cocoapods:

Simply add the following line to your Podfile:

pod 'Deli', '~> 0.8.1'

Carthage:

github "kawoou/Deli"

Command Line

$ deli help
Available commands:

   build      Build the Dependency Graph.
   generate   Generate the Dependency Graph.
   help       Display general or command-specific help
   upgrade    Upgrade outdated.
   validate   Validate the Dependency Graph.
   version    Display the current version of Deli

Examples

Contributing

Any discussions and pull requests are welcomed.

If you want to contribute, submit a pull request.

Requirements

  • Swift 3.1+

Attributions

This project is powered by

License

Deli is under MIT license. See the LICENSE file for more info.

Comments
  • Using other modules dependency

    Using other modules dependency

    1. Generate resolved data

    Deli binary:

    deli build --resolve-file true
    

    deli.yml:

    target:
      - MyProject
    
    config:
      MyProject:
        project: MyProject
        
        # ...
    
        # >>>
        resolve:
          output: Deli.resolved
          generate: true
        accessControl: public
        # <<<
    

    DeliFactory.swift:

    public final class DeliFactory: ModuleFactory {
        public override func load(context: AppContext) {
            // ...
        }
    }
    

    Deli.resolved:

    Generated resolving data of dependency.

    VERSION: 0.8.1
    DEPENDENCY:
    - TYPE: GitHubServiceImpl
      LAZILY: false
      FACTORY: false
      VALUE_TYPE: false
      DEPENDENCY:
      - TYPE: NetworkManager
        QUALIFIER: 
      LINK:
      - GitHubService
    - TYPE: NetworkManagerImpl
      LAZILY: false
      FACTORY: false
      VALUE_TYPE: false
      DEPENDENCY: []
      LINK:
      - NetworkManager
    - TYPE: ViewController
      LAZILY: false
      FACTORY: false
      VALUE_TYPE: false
      DEPENDENCY:
      - TYPE: ViewModel
        QUALIFIER: 
      LINK: []
    - TYPE: ViewModel
      LAZILY: false
      FACTORY: false
      VALUE_TYPE: false
      DEPENDENCY:
      - TYPE: GitHubService
        QUALIFIER: 
      LINK: []
    PROPERTY: {}
    PROJECT: GitHubSearch
    REFERENCE: GitHubSearch
    

    2. Load resolved data

    deli.yml:

    target:
      - MyProject
    
    config:
      MyProject:
        project: MyProject
        
        # ...
    
        # >>>
        dependencies:
          - path: Dependency files...
            imports: UIKit
        # <<<
    

    3. Add method that inject dependency from string class

    protocol BaseProtocol {}
    class ImplementClass: BaseProtocol, Component {}
    
    // ...
    
    let appContext = AppContext.shared
    let instance: BaseProtocol? = appContext.get(BaseProtocol.self, "ImplementClass")
    

    4. Add to get property with type method

    let port: Int? = AppContext.shared.getProperty("server.port", Int.self)
    

    5. Support ConfigProperty type property

    struct ServerConfig: ConfigProperty {
        let method: String?
        let url: String
        let port: Int
    
        // Remove under
        //
        // init(method: String, url: String, port: String) {
        //     self.method = method
        //     self.url = url
        //     self.port = Int(port) ?? 0
        // }
    }
    
    // ...
    
    let type: ServerInfo? = AppContext.shared.getProperty("server", ServerInfo.self)
    

    6. Fix typo

    • ResolveRule to ResolveRole

    7. Fix property merging bug when multi-module environment.

    8. Fix specific local path bug.

    • Directory/1. TestDirectory/Test1.swift
    • Directory/2. TestDirectory/Test2.swift

    9. Fix to searches source-code path bug.

    10. Support swift 5+

    11. Support nested type.

    import Deli
    
    class A {
        struct B: Component {
            ...
        }
    }
    
    class C: Autowired {
        required init(_ ab: A.B) {
            ...
        }
    }
    

    12. Support typealias keyword

    class A {
        struct B: Component {
            ...
        }
    }
    
    struct C {
        class D: Component {
            struct E {
                class F: Component {
                    ...
                }
            }
        }
    }
    class NestedTestClass: Autowired {
        typealias NestedType = C.D.E.F
    
        let a: A.B
        let b: C.D
        let c: NestedType
    
        required init(_ a: A.B, _ b: C.D, _ c: NestedType) {
            ...
        }
    }
    

    13. Implement property wrapper feature

    struct A {
        @Dependency
        var userService: UserService
    
        @PropertyValue("server.url")
        var serverURL: String
    
        func login() {
            userService.login()
        }
    
        init() {
            print(serverURL)
        }
    }
    

    14. Update libraries

    • SourceKitten: 0.21.2 -> 0.28.0
    • Yams: 1.0.0 -> 2.0.0
    • Regex: 1.1.0 -> 1.2.0
    • xcodeproj: 6.0.0 -> 7.5.0
    • Commandant: 0.15.0 -> 0.17.0
    • Nimble: 7.3.1 -> 8.0.2
    • Quick: 1.3.2 -> 2.2.0

    15. TODO

    • [x] Write section on README.md
    enhancement 
    opened by kawoou 1
  •  Fix quote replacing bug when export property configuration.

    Fix quote replacing bug when export property configuration.

    1. Fix quote replacing bug when export property configuration.

    application.json:

    {
        "applicationName": "1\"chicken\"2"
    }
    

    DeliFactory.swift:

    final class DeliFactory: ModuleFactory {
        override func load(context: AppContext) {
            loadProperty([
                "applicationName": "1"chicken"2"  //< Build failure
            ])
            ...
        }
    }
    

    2. Fix merging dictionary issues.

    application-1.json:

    {
        "server": {
            "addr": "http://www.github.com/"
        },
        "chicken": ["a", "b", "c", "d"]
    }
    

    application-2.json:

    {
        "server": {
            "port": 8080
        },
        "chicken": ["1", "2", "3", "4"]
    }
    

    DeliFactory.swift:

    final class DeliFactory: ModuleFactory {
        override func load(context: AppContext) {
            loadProperty([
                "server": [
                    "port": "8080"  //< Not overrided `addr`
                ],
                "chicken": ["1", "2", "3", "4"]
            ])
            ...
        }
    }
    
    bug 
    opened by kawoou 1
  • Support configuration property.

    Support configuration property.

    from #32

    • [x] ConfigProperty
    struct ServerConfig: ConfigProperty {
        let target: String = "server"
        let url: String
    }
    
    • [x] InjectProperty()
    final class NetworkManager: Inject {
        let serverUrl = InjectProperty("server.url")
    }
    
    • [x] Injection (Autowired, LazyAutowired, AutowiredFactory, LazyAutowiredFactory)
    final class UserService: Autowired {
        required init(_/*environment*/ network: NetworkProvider) {
        }
    }
    
    • [x] Injection (Inject)
    final class LibraryFactory: Inject {
        func getBooks() -> [Book] {
            return Inject([Book].self, qualifierBy: "environment")
        }
    }
    
    • [x] Optional get property
    final class InjectPropertyTest: Component {
        let test: Any? = AppContext.shared.getProperty("server.test")
        required init() {}
    }
    
    enhancement 
    opened by kawoou 1
  • Support struct.

    Support struct.

    Feature

    • Support struct.
    • Support Inject in Configuration.
    final class TestConfiguration: Configuration {
        let test = Config(Test.self) {
            let friendService = Inject(FriendService.self)
            return Test(friendService)
        }
    }
    
    • Add error message that injector cannot specify mutating keyword.
    struct StructWithLazyAutowired: LazyAutowired {
        //< Error: `inject()` method cannot specify mutating keyword.
        mutating func inject(_ friendService: FriendService) { ... }
    }
    
    • Add feature that build target configuration.
    target:
      - Deli
    
    config:
      Deli:
        project: Deli
        target: DeliTests
        output: Tests/DeliTests/DeliSample
    

    Test

    • Add test code for struct type.
    • Fixed an issue that operation was not completed during intermittent when testing the multi-thread environment.

    Others

    • Remove "as AnyObject"
    register(
        AccountService.self,
        resolver: {
            let parent = context.get(AccountConfiguration.self, qualifier: "")!
            return parent.accountService() /*as AnyObject*/
        },
        qualifier: "facebook",
        scope: .singleton
    ).link(AccountService.self)
    
    • Fix qualifier parsing bug on LazyAutowired.
    //< Error: Not found implementation on `ClassName` with qualifier ``.
    func inject(qualifierName name: Type) { ... }
    
    enhancement 
    opened by kawoou 1
  • Support feature and fix bugs.

    Support feature and fix bugs.

    • [x] Support another framework on Configuration.
    • [x] Support single payload AutowiredFactory.
    • [x] Fix Unicode offset bug.
    • [x] Fix dependency name bug.
    • [x] Fix missing qualifier bug.
    • [x] Fix ambiguous constructor bug.
    • [x] Fix path of the source file in parser bug.
    • [x] Fix reset issues.
    bug enhancement 
    opened by kawoou 1
  • Support user-arguments.

    Support user-arguments.

    Support user-arguments. #4

    In the front-end, often dynamically generating a model using the user's data. Let's take an example.

    You must implement a friend list. When you select a cell from friends list, you need to present modal view of friend's information. In this case, The friend data must be passed in the Info Modal.

    This happens very often, Factory will help them.

    Let's try AutowiredFactory protocol:

    class FriendPayload: Payload {
        let userID: String
        let cachedName: String
        
        required init(with argument: (userID: String, cachedName: String)) {
            userID = argument.userID
            cachedName = argument.cachedName
        }
    }
    
    class FriendInfoViewModel: AutowiredFactory {
        let accountService: AccountService
        
        let userID: String
        var name: String
        
        required init(_ accountService: AccountService, payload: FriendPayload) {
            self.accountService = accountService
            self.userID = payload.userID
            self.name = payload.cachedName
        }
    }
    

    To pass a user-argument, you must implement a Payload protocol. (Naturally, factories work by prototype scope)

    Implemented FriendInfoViewModel can be used as below:

    class FriendListViewModel: Autowired {
        let friendService: FriendService
        
        func generateInfo(by id: String) -> FriendInfoViewModel? {
            guard let friend = friendService.getFriend(by: id) else { return nil }
            
            return Inject(
                FriendInfoViewModel.self,
                with: (
                    userID: friend.id,
                    cachedName: friend.name
                )
            )
        }
        
        required init(_ friendService: FriendService) {
            self.friendService = friendService
        }
    }
    

    Next LazyAutowiredFactory protocol:

    class FriendInfoViewModel: LazyAutowiredFactory {
        var accountService: AccountService!
        
        func inject(facebook accountService: AccountService) {
            self.accountService = accountService
        }
        
        required init(payload: TestPayload) {
            ...
        }
    }
    

    The difference between an AutowiredFactory and a LazyAutowiredFactory is that it is lazy injected with the relationship between Autowired and LazyAutowired. However, payload injects by the constructor because passed by the user.

    • README.md: https://github.com/kawoou/Deli/pull/13/files#diff-04c6e90faac2675aa89e2176d2eec7d8
    • README_KR.md: https://github.com/kawoou/Deli/pull/13/files#diff-984a2a9c801501d6b9301edf77381e66
    enhancement 
    opened by kawoou 1
  • Support dependency injection for without lifecycle management.

    Support dependency injection for without lifecycle management.

    The lifecycle of UICollectionViewCell is managed by UICollectionView. In such a case, dependency injection must be possible.

    This rules in #4:

    class PhotoCollectionViewCell: UICollectionViewCell, Injectable {
        func inject(_ userService: Dependency<UserService>, _ userID: Argument<String>) {
            ...
        }
    }
    
    //-----------------------------------------------------------
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        ...
        cell.inject(with: (userID))
        ...
        return cell
    }
    

    We temporarily used the name "Injectable". Is there a good naming?

    enhancement 
    opened by kawoou 1
  • Support user-arguments.

    Support user-arguments.

    Payload:

    struct UserPayload: Payload {
        let userID: String
    
        required init(with argument: (userID: String)) {
            self.userID = argument.userID
        }
    }
    

    AutowiredFactory:

    class UserViewModel: AutowiredFactory {
        required init(
            _ testService: TestService,
            _ userService: UserService,
            payload: UserPayload
        ) {
            self.testService = testService
            self.userID = payload.userID
        }
    }
    
    //-----------------------------------------------------------
    
    class UserView: UIView, Inject {
        let viewModel: UserViewModel
    
        init(userID: String) {
            viewModel = Inject(UserViewModel.self, with: (userID: userID))
        }
    }
    

    LazyAutowiredFactory:

    class UserViewModel: LazyAutowiredFactory {
        func inject(
            _ testService: TestService,
            _ userService: UserService,
            payload: UserPayload
        ) {
            self.testService = testService
            self.userID = payload.userID
        }
        required init() {}
    }
    
    //-----------------------------------------------------------
    
    class UserView: UIView, Inject {
        let viewModel: UserViewModel
    
        init(userID: String) {
            viewModel = Inject(UserViewModel.self, with: (userID: userID))
        }
    }
    

    If specified as a Factory, automatically operates as a prototype scope.

    enhancement 
    opened by kawoou 1
  • Support configuration property.

    Support configuration property.

    How to build

    Change the Deli configuration file or include it via command line.

    application-dev.properties:

    environment: dev
    server:
        url: https://dev.example.com/
    

    deli.yml:

    target:
    - App
    
    config:
      App:
        - project: App
        - properties:
          - Resources/Common/*.properties
          - Resources/application-dev.properties # override
    

    command line:

    deli build \
      --property "Resource/application-dev.properties" \
      --property "Resources/Common/*.properties"
    

    How to use

    1. Package

    ServerConfig.swift:

    struct ServerConfig: ConfigProperty {
        let target: String = "server"
        let url: String
    }
    

    NetworkManager.swift:

    final class NetworkManager: Autowired {
        let serverUrl: String
    
        required init(_ config: ServerConfig) {
            serverUrl = config.url
        }
    }
    

    2. Single (without Validate)

    NetworkManager.swift:

    final class NetworkManager {
        let serverUrl = AppContext.getProperty("server.url") ?? ""
    }
    

    3. Single (with Validate)

    NetworkManager.swift:

    final class NetworkManager: Inject {
        let serverUrl = InjectProperty("server.url")
    }
    

    4. Qualifier on Constructor Injection

    UserService.swift:

    final class UserService: Autowired {
        required init(_/*environment*/ network: NetworkProvider) {
        }
    }
    

    5. Qualifier on Inject

    LibraryFactory.swift:

    final class LibraryFactory: Inject {
        func getBooks() -> [Book] {
            return Inject([Book].self, qualifierBy: "environment")
        }
    }
    

    Reference

    • https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html
    • https://laravel.com/docs/5.7/configuration
    • https://github.com/yyvess/Guice-configuration
    enhancement 
    opened by kawoou 0
  • Improved loading flow for ModuleFactory

    Improved loading flow for ModuleFactory

    The Test Mode function is inconvenient for writing test code. Therefore, want to improve the ModuleFactory so that it can be simplified.

    class SomeControllerTests: QuickSpec {
        override func spec() {
            super.spec()
    
            var sut: SomeController!
    
            var mockUserService: MockUserService!
            var mockFriendService: MockFriendService!
    
            let testModule = ModuleFactory()
            testModule.register(MockUserService.self) { MockUserService() }
                .link(UserService.self)
            testModule.register(MockFriendService.self) { MockFriendService() }
                .link(FriendService.self)
    
            beforeEach {
                AppContext.shared.load(testModule, priority: .high)
            }
            afterEach {
                AppContext.shared.reset()
                AppContext.shared.unload(testModule)
            }
    
            describe("SomeController") {
                beforeEach {
                    sut = AppContext.shared.get(SomeController.self)
                    mockUserService = AppContext.shared.get(MockUserService.self)
                    mockFriendService = AppContext.shared.get(MockFriendService.self)
                }
                ...
            }
        }
    }
    
    enhancement 
    opened by kawoou 0
  • Support to add target as dependency

    Support to add target as dependency

    1. Support to add target as dependency.

    deli.yml:

    target:
    - MyProject
    - OtherProject
    
    config:
      OtherProject:
        project: OtherProject
    
        # ...
    
      MyProject:
        project: MyProject
        
        # ...
    
        # >>>
        dependencies:
        - target: OtherProject
        # <<<
    

    2. Add reverse propagation that find the dependency.

    When an implementation is injected as a protocol from a submodule other than the module where the object is implemented, makes connections between corresponding protocols and objects possible in submodules.

    Untitled Diagram

    3. Support to inject payload for the Factory.

    struct UserPayload: Payload { ... }
    // ...
    class UserViewModel: AutowiredFactory {
        // ...
        init(payload: UserPayload) { ... }
        // ...
    }
    // ...
    class TestViewModel: Inject {
        // ...
        func makeUserViewModel(_ payload: UserPayload) -> UserViewModel {
            Inject(UserViewModel.self, with: payload)
        }
        // ...
    }
    

    4. Support protocol composition.

    typealias CombineProtocol = AProtocol & BProtocol
    

    5. Bug fixes

    • Parsing nested-type bug in the extension.
    • Multiple module load bug

    6. Add SwiftLint comment in factory code that generated.

    https://github.com/kawoou/Deli/blob/e05dd0bb1512316412c51da2949d9fcd12e59b16/Examples/Survey/Survey/SurveyFactory.swift#L6-L10

    bug enhancement 
    opened by kawoou 0
Releases(v0.9.0)
Owner
Jungwon An
Jungwon An
Dip is a simple Dependency Injection Container.

Dip is a simple Dependency Injection Container. It's aimed to be as simple as possible yet p

Olivier Halligon 949 Jan 3, 2023
DIContainer Swift is an ultra-light dependency injection container made to help developers to handle dependencies easily. It works with Swift 5.1 or above.

?? DIContainer Swift It is an ultra-light dependency injection container made to help developers to handle dependencies easily. We know that handle wi

Victor Carvalho Tavernari 10 Nov 23, 2022
ViperServices - Simple dependency injection container for services written for iOS in swift supporting boot order

ViperServices Introduction ViperServices is dependency injection container for iOS applications written in Swift. It is more lightweight and simple in

Siarhei Ladzeika 5 Dec 8, 2022
A new approach to Container-Based Dependency Injection for Swift and SwiftUI.

A new approach to Container-Based Dependency Injection for Swift and SwiftUI. Why do something new? Resolver was my first Dependency Injection system.

Michael Long 573 Jan 2, 2023
Container is a lightweight dependency injection framework for Swift.

Container Container is a lightweight dependency injection framework for Swift. Installation is available in the Swift Package Manager. Swift Package M

Aleksei Artemev 17 Oct 13, 2022
Injection - Dependency injection using property wrappers

Dependency injection using property wrappers. Registering types: // injecting a

Alejandro Ramirez 3 Mar 14, 2022
Perform - Easy dependency injection for storyboard segues

Perform Easy dependency injection for storyboard segues. import Perform // ... func tableView(_ tableView: UITableView, didSelectRowAt indexPath: NS

thoughtbot, inc. 280 Feb 6, 2022
Cleanse is a dependency injection framework for Swift.

Cleanse - Swift Dependency Injection Cleanse is a dependency injection framework for Swift. It is designed from the ground-up with developer experienc

Square 1.7k Dec 16, 2022
Corridor A Coreader-like Dependency Injection μFramework

Corridor A Coreader-like Dependency Injection μFramework Table of Contents Why | Examples | Usage | Installation | Credits & License | Why In order to

symentis GmbH 60 Nov 1, 2022
DIKit Dependency Injection Framework for Swift, inspired by KOIN.

DIKit Dependency Injection Framework for Swift, inspired by KOIN. Basically an implementation of service-locator pattern, living within the applicatio

null 95 Dec 22, 2022
Tranquillity is a lightweight but powerful dependency injection library for swift.

DITranquillity Tranquillity is a lightweight but powerful dependency injection library for swift. The name "Tranquillity" laid the foundation in the b

Ivlev Alexander 393 Dec 24, 2022
Swinject is a lightweight dependency injection framework for Swift.

Swinject Swinject is a lightweight dependency injection framework for Swift. Dependency injection (DI) is a software design pattern that implements In

null 5.6k Dec 31, 2022
Typhoon Powerful dependency injection for Cocoa and CocoaTouch.

Typhoon Powerful dependency injection for Cocoa and CocoaTouch. Lightweight, yet full-featured and super-easy to use. Pilgrim is a pure Swift successo

AppsQuick.ly 2.7k Dec 14, 2022
Dependency Injection framework for Swift (iOS/macOS/Linux)

Declarative, easy-to-use and safe Dependency Injection framework for Swift (iOS/macOS/Linux) Features Dependency declaration via property wrappers or

Scribd 684 Dec 12, 2022
Swift Ultralight Dependency Injection / Service Locator framework

Swift Ultralight Dependency Injection / Service Locator framework

Michael Long 1.9k Jan 6, 2023
A simple way to handle dependency injection using property wrappers

Injektion Introduction A simple way to handle dependency injection using propert

Andrew McGee 2 May 31, 2022
Reliant - Nonintrusive Objective-C Dependency Injection

Reliant Reliant is a Dependency Injection (DI) framework for Objective-C, both for OS X and iOS. Its goal is to make its use as simple as possible, wh

AppFoundry 52 Oct 14, 2022
Pilgrim - Dependency injection for Swift (iOS, OSX, Linux). Strongly typed, pure Swift successor to Typhoon.

pilgrim.ph Pilgrim is a dependency injection library for Swift with the following features: Minimal runtime-only library that works with pure Swift (s

AppsQuick.ly 60 Oct 24, 2022
StoryboardBuilder - Simple dependency injection for generating views from storyboard.

StoryboardBuilder Simple dependency injection for generating views from storyboard. Description StoryboardBuilder is framework to help simply and easi

null 5 Jun 13, 2019