Swinject: Registering protocol with associated types

Created on 18 Feb 2017  路  7Comments  路  Source: Swinject/Swinject

Hey - I do have such protocols defined:

protocol SessionService {
    associatedtype T
}

class SessionServiceDefault : SessionService {
    typealias T = Int

}

I cannot find a way to register it properly.

container.register(SessionService.self) { _ in SessionServiceDefault() }

That returns an error:

Protocol 'SessionService' can only be used as a generic constraint because it has Self or associated type requirements

question

Most helpful comment

You can paste this directly to the playground in the Swinject project.
The idea is to use boxing and type erasing. It works, however you have to wrap it.

Refer to boxing technique for iOS.
https://www.natashatherobot.com/swift-generics-box/

```import Swinject

protocol Animal {
associatedtype T
}

class AnyTypedAnimal: Animal {
typealias T = Any
}

struct Box {
typealias U = T

let objectType: U.Type
let object: U

init(object: U) {
    self.object = object
    self.objectType = U.self
}

}

final class Cat: Animal {
typealias T = Int
}

final class Mouse: Animal {
typealias T = String
}

let container = Container()
container.register(Box.self) { _ in Box(object: Cat()) }
container.register(Box.self) { _ in Box(object: Mouse()) }

let myCatInstance = container.resolve(Box.self)
let mouseInstance = container.resolve(Box.self)
print(container)

All 7 comments

Thanks @grzegorzkrukowski for pointing out the interesting error.

This is a limitation of a protocol with associated types. You get the same error if you try defining a dictionary with that protocol like:

let dict: [SessionService: Any] // Generates the same error.

Swinject also cannot use a protocol with associated types unfortunately馃槩

@grzegorzkrukowski please feel free to reopen this issue or open another issue if you find more problems.

i encountered the same problem. Definitly unresolvable?

@jakubvano Do you know some workaround for this please?

You can paste this directly to the playground in the Swinject project.
The idea is to use boxing and type erasing. It works, however you have to wrap it.

Refer to boxing technique for iOS.
https://www.natashatherobot.com/swift-generics-box/

```import Swinject

protocol Animal {
associatedtype T
}

class AnyTypedAnimal: Animal {
typealias T = Any
}

struct Box {
typealias U = T

let objectType: U.Type
let object: U

init(object: U) {
    self.object = object
    self.objectType = U.self
}

}

final class Cat: Animal {
typealias T = Int
}

final class Mouse: Animal {
typealias T = String
}

let container = Container()
container.register(Box.self) { _ in Box(object: Cat()) }
container.register(Box.self) { _ in Box(object: Mouse()) }

let myCatInstance = container.resolve(Box.self)
let mouseInstance = container.resolve(Box.self)
print(container)

Protocols with associated types can directly be referenced only as generics constraints - thus you cannot reference them directly neither in Swinject nor constructor parameters.

As @radimhalfar pointed out, boxing is one way around that.
However in DI cases you would more likely need something like Generalized existentials, which I guess we will have to wait for for quite some time...

Until then I tend to use something like this:

protocol ServiceProtocol {
      associatedtype Model
      func doSomething(with model: Model)
}

class Service<Model>: ServiceProtocol {
    func doSomething(with model: Model) {
        fatalError()
    }
}

typealias IntService = Service<Int>

final class IntServiceImpl: IntService {
   func doSomething(with model: Int) {
      // real implementation
   }
}

container.autoregister(IntService.self, initializer: IntServiceImpl.init)

It is kinda ugly, doesn't work in all cases and can take a while to get used to, but it is the best I've came up with for tackling this kind of problem.

@radimhalfar @jakubvano Thanks guys 馃憤

Was this page helpful?
0 / 5 - 0 ratings