2 min read
Containers
Last Updated - Platform 25.0 - SDK 20.0Containers are the easiest way to inject custom code. The shared container provides an easy way replace or decorate the dependencies of SDK features by injecting factory closures.
For bespoke features, the shared container can be extended with custom dependencies to provide a consistent dependency injection interface.
Containers are intended for global use. To replace a dependency of a single instance of a feature you should use that features initialiser or builder directly.
Supported Types
Data and domain layer objects:
Container.shared.mappers.{mapper}
Container.shared.repositories.{repository}
Container.shared.services.{service}
Presentation layer objects:
Container.shared.coordinators.{coordinator}
Container.shared.dataSources.{viewDataSource}
for table, collection, and stack view data sourcesContainer.shared.mappers.{viewDataMapper}
Container.shared.views.{view}
Special objects:
Container.shared.navigator
for global navigation handling (defaults toNavigationHelper
)Container.shared.networkSession
for the global network sessionContainer.shared.resourceResolver
for global resource resolution
Scopes
Containers support two scopes:
- Factory scope for
Factory
dependencies which will always return a new instance. - Singleton scope for
SingletonFactory
dependencies which will always return the same instance.
The SingletonFactory
supports both scope types so you can inject a closure to always return new instances into them if needed.
Singleton scope is the default for all SDK dependencies except for views and data sources.
Injection
To inject a custom factory for a SingletonFactory
dependency you can use any of the following.
Replace will act the same as .single
for SingletonFactory
dependencies.
Container.shared.mappers.someMapper = .single { CustomMapper() }Container.shared.mappers.someMapper = .factory { CustomMapper() }Container.shared.mappers.someMapper.replace { CustomMapper() }
To inject a custom factory for a Factory
dependency you can use either of the following.
Replace will act the same as .factory
for Factory
dependencies.
Container.shared.views.someView = .factory { CustomView() }Container.shared.views.someView.replace { CustomView() }
Decoration
If you need to add behaviour to a dependency without changing much else you can use the decorator pattern.
Use .decorate
to modify the object using its interface or decorate it in your own object.
Container.shared.mappers.someMapper.decorate { CustomMapper(decorating: $0) }Container.shared.mappers.someMapper.decorate(CustomMapper.init)
Container.shared.dataSources.someDataSource.decorate { var dataSource = $0 dataSource.itemSize = CGFloat(width: 400, height: 200) return dataSource}
Usage
To retrieve an instance of an object here are a few examples.
Views have access to a convenient static variable container
without needing Container.shared
.
let mapper = Container.shared.mappers.someMapper()
class CustomMapper: SomeMapper { init(subMapper: SubMapper = Container.shared.mappers.subMapper()) { ... }}
class CustomView: UIView { lazy var anotherView = container.views.anotherView() init(someView: SomeView = container.views.someView()) { ... }}
To retrieve the default Poq implementation of an object use Container.poq.mappers.someMapper()
.
It is unlikely that you will need to use this.
Custom Dependencies
Dependencies can be injected directly into containers without using the strongly typed variables. But this can be easily error prone if you make mistakes with the key.
Container.shared.inject(key: "Something", object)Container.shared.inject(object)
Dependencies can be resolved in the same way using the following or similar convenience functions.
Container.shared.resolve(Something.self, key: "Something")Container.shared.resolve(Something.self)
However, for custom shared features, to get the best out of containers you can use extensions and namespaces. To create custom strongly typed dependencies use the following.
public extension Container.Mappers { var someMapper: SingletonFactory<SomeMapper> { get { resolve { PoqPriceViewDataMapper() } } set { inject(newValue) } }}
Remember for Views
or DataSources
you must use Factory
instead of SingletonFactory
.
Using single instances for views is bound to cause a crash due to sharing the same view.
To create custom namespaces use the following.
public extension Container { enum NamespaceCustom {} typealias Custom = Namespace<NamespaceCustom> var custom: Custom { namespace() }}
// The above will allow the following.Container.shared.custom.something()