Skip to content

Type forwarding

Ilya Puchka edited this page Sep 14, 2016 · 6 revisions

Single responsibility principle states that each class/function should have only one responsibility or reason to change. Though we should always keep this principle in mind very often it becomes unpractical to strictly follow it. Then we end up with single class having several responsibilities. We use protocols to define those responsibilities, so this class will conform to several protocols at the same time. When resolving the objects graph we, most likely, want our components to reference the same instance but through different protocols.

There are few ways to setup container to be able to resolve such objects graph. First is to register and resolve concrete types. Let's say we have some class ListPresenter that implements several protocols:

extension ListPresenter: ListInteractorOutput, ListModuleInterface, AddModuleDelegate { ... }

Then when we register this component we can register it using its concrete type:

container.register(.Shared) { ListPresenter() }

When we need to resolve any of the protocols that ListPresenter conforms to we can resolve it using its concrete type.

class AddPresenter: NSObject {
    var addModuleDelegate: AddModuleDelegate?
}

presenter.addModuleDelegate = try container.resolve() as ListPresenter

That is not an ideal approach because if we decide to extract some of these protocols conformance to a separate class we will need to change our registrations in several places.

Another way is to use resolve inside factories to register protocols. We can again register concrete type and use it to resolve protocols:

container.register(.Shared) { ListPresenter() }
container.register(.Shared) { try container.resolve() as ListPresenter as ListInteractorOutput }
container.register(.Shared) { try container.resolve() as ListPresenter as ListModuleInterface }
container.register(.Shared) { try container.resolve() as ListPresenter as AddModuleDelegate }

//or
container.register(.Shared, type: ListInteractorOutput.self) { try container.resolve() as ListPresenter }
container.register(.Shared, type: ListModuleInterface.self) { try container.resolve() as ListPresenter }
container.register(.Shared, type: AddModuleDelegate.self) { try container.resolve() as ListPresenter }

But that will leave us with redundant definition for concrete type and will make registrations for protocols less readable. Also it still has the same issue as previous approach.

The last approach is to use type forwarding. Using type forwarding we can reuse single definition to resolve several types.

let listPresenter = container.register(.Shared) { ListPresenter() }
container.register(listPresenter, type: ListInteractorOutput.self)
container.register(listPresenter, type: ListModuleInterface.self)
container.register(listPresenter, type: AddModuleDelegate.self)

//or
container.register(.Shared) { ListPresenter() }
  .implements(ListInteractorOutput.self, ListModuleInterface.self, AddModuleDelegate.self)

Now when resolving any of these types container will use the same definition and as it is registered in Shared scope will reuse instance resolved for one of implementing types in a single object graph.

Unfortunately Swift does not (currently) provide a way to ensure type safety of such registrations. It will not warn you if the component resolved by definition does not implement type that you try to resolve. That will cause a container to throw InvalidType error.

When registering type-forwarding definition container will effectively create another definition and establish a link between it and the original definition. So the same overriding rules are applied for such definitions. You can tag them and provide custom resolvingProperties block (it will be called after resolvingProperties callback of original definition).

Container also uses type-forwarding internally to be able to resolve optionals.