Conforming Protocol and Default Implementation Issue (Playground Code attached)

Having an issue using a protocol with a default implementation (DefaultControllerPresentationProtocol down below). This protocol requires that a conforming object have a var that conforms to another protocol (ie Presentable) in order to provide the default implementation.

However, in practice, the class I want to conform to Presentable has a var that is defined as a different protocol type that conforms to Presentable rather than Presentable itself (ie. protocol ControllerOutputable: Presentable).

It seems the compiler doesn't like that the var is not explicitly Presentable but rather another protocol that conforms to presentable. Playground output is:


error: ProtocolCompilerError.playground:37:7: error: type 'Controller' does not conform to protocol 'DefaultControllerPresentationProtocol'
class Controller: ControllerProtocol {
      ^

ProtocolCompilerError.playground:39:7: note: candidate has non-matching type 'ControllerOutputable?'
  var output: ControllerOutputable!
      ^

ProtocolCompilerError.playground:16:7: note: protocol requires property 'output' with type 'Presentable?'; do you want to add a stub?
  var output: Presentable! { get set }
      ^

Is this a limitation of the compiler? Am I missing something obvious? Code below can be pasted into playgrounds to see the error


// We have a protocol to ensure our controllers can start a present 
protocol ControllerPresentationProtocol {
  func present()
}

// We have another protocol to ensure the output of the controller can perform presenting
protocol Presentable {
  func presenting()
}

// Usually this implementation is the reused as long as the output conforms to Presentable
// therefore, we create a new protocol with a default implementation
protocol DefaultControllerPresentationProtocol: ControllerPresentationProtocol {
  var output: Presentable! { get set }
}

extension DefaultControllerPresentationProtocol {
  func present() {
    output.presenting()
  }
}

// As long as our `Controller` has an `output` that conforms to `Presentable`,
// we can add default implementation of `DefaultControllerPresentationProtocol`

protocol ControllerProtocol: DefaultControllerPresentationProtocol {
}

// Our controller output protocol conforms to Presentable

protocol ControllerOutputable: Presentable { }

// This is our class implementation of Controller conforming to ControllerProtocol

class Controller: ControllerProtocol { // Here we get a compiler error that says  "error: type 'Controller' does not conform to protocol 'DefaultControllerPresentationProtocol'"
                                      // The fix-it suggests adding a `var output: Presentable` although `ControllerOutputable` conforms to `Presentable`
  var output: ControllerOutputable!
}

// This is our output implementation

class ControllerOutput: ControllerOutputable {
  func presenting() {
    print("presenting")
  }
}

// Here we create the controller and its output

let controller = Controller()
controller.output = ControllerOutput()

controller.present() // This should print "presenting" but it fails to compile

Accessors used to satisfy the protocol must have the exact same type as the requirement, so you need output to be exactly Presentable!.

Even if we allow subtype-super type relations between the requirement and the declaration, it still won't work, because Controller.output needs to be subtype of Presentable! (to get) and be supertype of Presentable! (to set), but ControllerOutputable! is not a supertype of Presentable!.

You might need to manually implement the accessor if you really need output to be that type (kind of):

class Controller: ControllerProtocol {
  private var _output: ControllerOutputable!
  var output: Presentable! {
    get { _output }
    set { _output = newValue as! ControllerOutputable }
  }
}

Though it's probably better to have ControllerProtocol have read-only output:

protocol ControllerProtocol {
  var output: Presentable! { get }
}

output still needs to be exactly of type Presentable, of course.

1 Like

Wow! Thanks for the response @Lantua! This is really great insight. I think I'm going to go with a different implementation that avoids this issue in the first place. Not as clean given our architecture but doesn't require mucking around with the getters and setters every time we want this default implementation.

You might want to try converting ControllerOutputable to concrete type. I was going to suggest:

protocol ControllerProtocol {
  associatedtype Output: Presentable!

  var output: Output! { get set }
}

which would much more closely reflect your intention (AIUI). It won't work because Output needs to be concrete type, and ControllerOutputable is not a concrete type.

Unfortunately we need output to remain a protocol type ControllerOutputable to adhere to our broader unit testing strategy.

I see, that'll be a little tricky if you want each conforming type to have their own protocol (associated protocol?) since Swift doesn't really have that.


PS

Note that in my suggested setter, I used force casting:

set { _output = newValue as! ControllerOutputable }

This is what I meant when I said that ControllerOutputable needs to be supertype of Presentable. We wouldn't need conversion at all if that's the case.

Yeah I think we may just have a separate presentationOutput that is Presentable and that points to the same object. The benefit there is in the future we can have different objects do the presenting (ie a Router object).

Terms of Service

Privacy Policy

Cookie Policy