How in swift one should use DI when conforming to a protocol with associated type?

79 Views Asked by At

I want to achieve something like this in swift:

protocol P {}
protocol P1: P {}
class P1Impl: P1 {}

protocol PBase {
  associatedtype T: P
  var prop: T { get }
}

struct S: PBase {
  typealias T = P1 // <---- The problem

  // This property is got via DI, so I don't know
  // the actual type of it.
  @Injected var prop: P1
}

Now the problem is that this code won't compile and the reason is the line typealias T = P1. The compiler wants to know the actual type instead of P1 (while I don't have that type).

I would understand this restriction if the protocol P had a required static property. In that case we can get the value of that property without any instance so the actual type is really needed. However there is no such a property in my case.

Anyway, I like swift and want to solve this task in a swifty way. What is the best practice for this case?

P.S.

I thought generics could help:

struct S<T: P1>: PBase {
  var prop: T
}

However in this case also I don't know the type of T when creating an instance of S because of DI which returns a protocol.

In case of any P the code still doesn't compile, I get the same error as before:

protocol PBase {
  var prop: P { get }
}

struct S: PBase {
  @Injected var prop: P1
}

But there is a workaround, which I think is not the nicest solution.

protocol PBase {
  var prop: P { get }
}

struct S: PBase {
  @Injected var prop1: P1
  var prop: P { prop1 }
}

Also everything gets worse when another protocol which should be inherited from PBase is needed.

1

There are 1 best solutions below

4
Bram On

As mentioned in one of the comments, I do believe it might be worth checking out the any P return value and whether or not your associatedtype requirement is actually needed. And otherwise you could try to use type erasure:

struct AnyP1: P1 {
    var actual: any P1

    init(_ actual: any P1) {
        self.actual = actual
    }
}

This wraps the P1 implementation into a known type, which will also conform to the P1 protocol. Then in your S object, you should do the following:

struct S: PBase {
    typealias T: AnyP1

    var prop: T
}

// Or shorthand

struct S: PBase {
    var prop: AnyP1
}

Your S will now always know what the type of T will be, and the type erasure will contain the actual underlying protocol value.