Giter Site home page Giter Site logo

hmlongco / injectabledemo Goto Github PK

View Code? Open in Web Editor NEW
13.0 3.0 0.0 122 KB

Demonstration code for a simple Swift property-wrapper, keypath-based dependency injection system. The keypaths ensure compile-time safety for all injectable services.

Swift 100.00%
swiftui dependency-injection swift

injectabledemo's Introduction

Injectable Demo

Preliminary musings and demonstration code for a simple Swift property-wrapper, keypath-based dependency injection system. The keypaths ensure compile-time safety for all injectable services.

Injectable also supports overriding services for mocking and testing purposes, as well as a rudimentary thread-safe scoping system that enables unique, shared, cached, and application-level scopes for services.

Note

Some of the concepts discussed here are now used in my new dependency injection framework, Factory.

Demo Code

Here's a SwiftUI view that uses an injectable view model.

struct ContentView: View {
    
    @InjectableObject(\.contentViewModel) var viewModel
    
    var body: some View {
        VStack(spacing: 16) {
            Text("\(viewModel.id)")
                .font(.footnote)
            
            NavigationLink("Next", destination: ContentView())
        }
        .onAppear(perform: {
            viewModel.test()
        })
    }
}

And here's the code for the view model which in turn has its own injectable service.

class ContentViewModel {
    
    @Injectable(\.myServiceType) var service
    
    var id: String {
        service.service()
    }
    
    func test() {
        print(service.service())
    }
}

Note that MyServiceType is a protocol and as such can be overridden with other values for testing.

The service protocol, service, and a mock service appear as follows.

protocol MyServiceType  {
    func service() -> String
}

class MyService: MyServiceType {
    private let id = UUID()
    func service() -> String {
        "Service \(id)"
    }
}

class MockService: MyServiceType {
    private let id = UUID()
    func service() -> String {
        "Mock \(id)"
    }
}

Resolving the ViewModel and Services

Here's are the registrations that resolve the various keypaths.

extension Injections {
    var contentViewModel: ContentViewModel { shared( ContentViewModel() ) }
    var myServiceType: MyServiceType { shared( MyService() ) }
}

For each one we extend Injections to add a factory closure that will be called to provide a new instance of the viewmodel or service when needed.

Note that we're using shared scopes here in order to ensure persistance across view updates in SwiftUI.

Mocking and Testing

The key to overriding a given service for mocking and testing lies in adding a Resolver-style inferred-type registration factory that will override the keypath registration.

extension Injections {
    static func registerMockServices() {
        container.register { MockService() as MyServiceType }
        // others as needed
    }
}

Here's an example of the mocks being used in the ContentView preview.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Injections.registerMockServices()
        return ContentView()
    }
}

Injectable

And finally, here's part of the @Injectable property wrapper that demonstrates the basic technique used. The initialization function checks to see if an override exists (optional). If not it resorts to using the required keypath.

@propertyWrapper public struct Injectable<Service> {
    
    private var service: Service
    
    public init(_ keyPath: KeyPath<Injections, Service>) {
        self.service = Injections.container.resolve() ?? Injections.container[keyPath: keyPath]
    }
    
    ...
    
}

As the initializer requires the keypath, it must exist. Thus all registrations are required to exist, which ensures compile-time safety.

Overrides to the keypaths are exceptions to the rule, and are treated as such.

All of the code, including the code for the scopes, requires about 160 lines of code. That also includes an addtional property wrapper, @InjectableObject, which can be used in SwiftUI code like an ObservableObject.

The Idea

The impetus for this code and demo resolves around an article written by Antoine van der Lee, titled Dependency Injection in Swift using latest Swift features.

That article, in turn, triggered my own Medium article, I Hate Swift. I Love Swift, where I detailed some of my own attempts to solve some of the issues perceived in Antoine's original approach.

And this is the final result.

injectabledemo's People

Contributors

hmlongco avatar

Stargazers

Josh R avatar mui_z avatar  avatar Sabarinathan J avatar Amir Riyadh avatar  avatar Daniel Waylonis avatar John Nyquist avatar Farshad Mousalou avatar Tim Oliver avatar Zach avatar Parshav avatar Manh Pham Van (Opn VN - Dev Manager) avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.