Skip to content

egeniq/InjectPropertyWrapper

Repository files navigation

InjectPropertyWrapper

Provides a generic Swift @Inject property wrapper that can be used to inject objects / services from a dependency injection framework of your choice.

Basic Usage

First, you need to implement the Resolver protocol for the Dependency Injection (DI) framework you are using.

For example, when using Swinject:

extension Container: InjectPropertyWrapper.Resolver {
}

In case of Swinject the Container class already contains a method with the same signature (resolve<T>(_ type: T, name: String?)) as the InjectPropertyWrapper Resolver protocol requires.

Then you need to set the global resolver (for example in your app delegate):

let container = Container()
InjectSettings.resolver = container

Register some objects in the container:

container.register(APIClient.self) { _ in APIClient() }
container.register(MovieRepository.self) { _ in IMDBMovieRepository() }
container.register(MovieRepository.self, name: "netherlands") { _ in IMDBMovieRepository("nl") }

Now you can use the @Inject property wrapper to inject objects/services in your own classes:

class IMDBMovieRepository: MovieRepository {
    @Inject private var apiClient: APIClient
    
    ...
    
    func fetchTop10(completionHandler: @escaping (movies: [Movie]) -> Void) {
        ...
    }
}

class MovieViewModel: BindableObject {
    public var didChange = PassthroughSubject<Void, Never>()
    public private(set) var top10: [Movie]? {
        didSet {
            didChange.send()    
        }
    }

    @Inject private var movieRepository: MovieRepository
    
    func load() {
        movieRepository.fetchTop10() { [weak self] movies in
            self?.top10 = movies
        }
    }
}

It is also possible to inject different objects of the same type using the name parameter:

class MovieViewModel: BindableObject {
    ...
    @Inject private var globalMovieRepository: MovieRepository
    @Inject(name: "netherlands") private var nlMovieRepository: MovieRepository
    ...
}

Normally if the property wrapper is unable to resolve a dependency it will raise a non-recoverable fatal error. If for some reason you expect an object sometimes to be unavailable in your container, you can mark the property as optional:

class MovieViewModel: BindableObject {
    ...
    @Inject(name: "germany") private var deMovieRepository: MovieRepository?
    ...
}

Testing

To run the tests for this package make sure the ENABLE_TESTS environment variable is set to 1 or true. For example when using the command line:

ENABLE_TESTS=1 swift test

This allows the package to only load certain dependencies when testing.

License

This project is licensed under the terms of the MIT license. See the LICENSE file.