Reputation: 1657
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
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