Giles
Giles

Reputation: 1657

Why Must I Cast Away Protocol Conformance on Swift Class for Array Extension?

I am implementing a tree model. Each node type can have a wide selection of capabilities. I am implementing this with a base class Node {}, then many protocols, with default implementations, to describe the capabilities. I then have final classes, derived from Node, and conforming to some list of protocols, to describe my structure.

Each protocol has associated code in the following style:

protocol Protocol {}

typealias ProtocolConformingNode = Node & Protocol

extension Node
{
    public var isProtocolConforming: Bool {
        return asProtocolConforming != nil
    }

    public var asProtocolConforming: ProtocolConformingNode? {
        return self as? ProtocolConformingNode
    }
}

I use Node & Protocol so that I can use other protocol related queries on the returned items, e.g. asOtherProtocolConforming.

This all works fine, until I am dealing with arrays of ProtocolConformingNode. Initially I ran into problems filtering arrays of nodes + protocol, where I wanted to maintain the conformance after the filtering:

extension Array where Element: Node
{
    var someFilter: [Element] {
        return self
    }
}

let array = [ProtocolConformingNode]()
let filteredArray = array.someFilter

This gives the following error:

[ProtocolConformingNode]' (aka 'Array<Node & Protocol>') requires that 'ProtocolConformingNode' conform to 'AnyObject'

That felt a bit weird, but I think it falls under an issue discussed before on SO: Protocol doesn't conform to itself?

I decided that type-erasing all my protocols would be too much for the rare cases I used this, so I would be content with casting my nodes after filtering. Note that I am not using Element in the following code, but explicitly using Node, and Element == Node:

extension Array where Element == Node
{
    var someFilter: [Node] {
        return self
    }
}

let array = [ProtocolConformingNode]()
let filteredArray = array.someFilter as! [ProtocolConformingNode]

This now gives the error:

'[ProtocolConformingNode]' (aka 'Array<Node & Protocol>') is not convertible to 'Array<Node>'

Which feels strange to me. I can get this to work like this:

let filteredArray = (array as [Node]).someFilter as! [ProtocolConformingNode]

But why do I have to cast here? How is it that the below works?

var nodes = [Node]()
var protocolConformingNodes = [ProtocolConformingNode]()
nodes.append(contentsOf: protocolConformingNodes)

When:

mutating func append<S>(contentsOf newElements: S) where Element == S.Element, S : Sequence

Upvotes: 0

Views: 131

Answers (1)

deaton.dg
deaton.dg

Reputation: 1352

This is the same issue as the SO post you linked. In particular, you should read Hamish's answer. IMO, this is a shortcoming of the language that should only be a problem if Protocol has static or initializer requirements, which in this case, it does not.

The quick and dirty fix is to use @objc. The full code would look like

class Node {}

@objc protocol Protocol {}

typealias ProtocolConformingNode = Node & Protocol

extension Array where Element: Node
{
    var someFilter: [Element] {
        return self
    }
}

let array = [ProtocolConformingNode]()
let filteredArray = array.someFilter // Yay! No error this time.

Upvotes: 0

Related Questions