Ryan
Ryan

Reputation: 518

How to make a protocol that declares a property of a protocol type?

I am trying to leverage protocols and extensions in Swift, but am getting a compile error I can't make sense of.

If I declare two protocols that define a Shape like this:

protocol Shape {
  var sides : Int { get }
  var fill : Fill { get }
}

protocol Fill {
  var color : UIColor { get }
}  

Now to implement this, I define two structs one for a Square, and the other for a solid fill. Like this:

struct SolidFill : Fill {
  var color : UIColor
}

struct Square : Shape {
  var sides : Int = 4
  var fill : SolidFill = SolidFill(color: UIColor.blackColor())
}

I receive a compile error "Type 'Square' does not conform to protocol 'Shape'". If I force the type of fill to be fill like var fill : Fill the compile error goes away. Why can't I have Square specify a more specific type for fill, then what the protocol allows?

I realize that for this particular example, I could change things to not use this pattern by using an Enum for fill or reworking something else. I just want to know why I can't have fill be a type that conforms to the protocol.

Upvotes: 0

Views: 82

Answers (2)

nhgrif
nhgrif

Reputation: 62062

Because it wouldn't make sense.

Before you start writing code that conforms to protocols, you need to think about your protocols and realize that in a normal use case (and the way that your protocols should be designed), code will be written only with knowledge of the protocol and with zero knowledge of any specific class or struct which implements the protocol.

So, it does make sense for the Fill protocol to have other protocols that inherit from it, such as SolidFill and perhaps StripedFill.

Let's go ahead and add the StripedFill protocol to your example. Now let's look at your Square struct and see why it doesn't implement the Shape protocol, which in part requires having a Fill property.

struct Square : Shape {
  var sides : Int = 4
  var fill : SolidFill = SolidFill(color: UIColor.blackColor())
}

The only thing we can assign to Square's fill property are things which implement the SolidFill protocol. But the Shape protocol requires that our shape be able to assign to a property called fill anything which conforms to the Fill protocol. And in the case where we also have a protocol called StripedProtocol which inherits from the Fill protocol, that would include objects that implement that protocol (whether or not they also implement the SolidFill protocol).

But your Square class doesn't allow for this. Your Square class only allows for one specific child of Fill and its descendants, but not its sibling... and the exclusion of SolidFill's potential siblings is why you can't do what you're trying to do.

What you can do however is this:

struct Square: Shape {
    var sides: Int = 4
    var fill: Fill = SolidFill(color: UIColor.blackColor())
}

So in this case, we're still definitely assigning a SolidFill to our Square, but we're still allowing for the fill property to match what it defined in the protocol and allowing for SolidFill's siblings to be assigned to the fill property.

Upvotes: 1

Joseph Lord
Joseph Lord

Reputation: 6504

With only a getter it may be theoretically OK but if there was a setter it would definitely not fulfil the protocol as someone may try to set a different object that fulfils the protocol.

If you think it is sufficiently valuable you could raise a radar requesting it for get only vars but you are probably better working around it.

You could do this:

struct Square : Shape {
  var sides : Int = 4
  var fill : Fill { solidFill }
  private var solidFill = SolidFill(color: UIColor.blackColor())
}

Upvotes: 0

Related Questions