I'm trying to design a strongly typed hierarchy of objects using protocols, but can't quite get it right.
For illustration, suppose that the concrete types that will end up adopting these protocols are the classes Country
, State
, and City
.
Each node can have a parent (except the root object) and/or children (except the leaf objects): All State
instances are children of the single Country
instance and have it as parent, and they have City
instances as children, etc.
So I start with these two protocols:
/// To be adopted by State and City
///
protocol Child: AnyObject {
associatedtype ParentType: AnyObject
// (-> so that property `parent` can be weak)
weak var parent: ParentType? { get }
}
/// To be adopted by Country and State
///
protocol Parent: AnyObject {
associatedtype ChildType: AnyObject
// (-> for symmetry)
var children: [ChildType] { get }
}
I have two separate protocols instead of one that groups all the above requirements, because I don't want to have to specify a "dummy" typealias ParentType
for the root class Country
(which is meaningless), nor a "dummy" typealias ChildType
for the leaf class City
.
Instead, I can separate the parent and child behaviours and just have the intermediate class State
adopt both protocols.
Next, I want to make my clases initializable from a dictionary read from disk. For the child classes, I want to specify the parent at the moment of instantiation, so I came up with this scheme:
protocol UnarchivableChild: Child {
init(dictionary: [String: Any], parent: ParentType?)
}
protocol UnarchivingParent: Parent {
associatedtype ChildType: UnarchivableChild
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildType]
}
As it stands, it looks like I could go one step further and add a default implementation of the method readChildren(fromDictionaries:)
, like this:
extension UnarchivingParent {
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildType] {
return dictionaries.flatMap({ dictionary in
return ChildType(dictionary: dictionary, parent: self)
})
}
}
...because in this protocol, ChildType
is constrained to UnarchivableChild
, so it should support the initializer...? But I get:
Cannot invoke 'ChildType' with an argument list of type '(dictionary: ([String : Any]), parent: Self)'
(why the capitalized "Self"?)
I think I am missing something about how associated types work...
How can I code this default implementation?
Update: Apparently, passing self is the problem somehow. I modified the code to:
protocol Node: AnyObject {
}
protocol Parent: Node {
associatedtype ChildNodeType: Node
var children: [ChildNodeType] { get set }
}
protocol Child: Node {
associatedtype ParentNodeType: Node
weak var parent: ParentNodeType? { get set }
}
protocol UnarchivableChild: Child {
init(dictionary: [String: Any]) // Removed parent parameter!
}
protocol UnarchivingParent: Parent {
associatedtype ChildNodeType: UnarchivableChild
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildNodeType]
}
extension UnarchivingParent {
func readChildren(fromDictionaries dictionaries: [[String: Any]]) -> [ChildNodeType] {
return dictionaries.flatMap({
let child = ChildNodeType(dictionary: $0)
// Assign parent here instead:
child.parent = self // < ERROR HERE
return child
})
}
}
The error is:
Cannot assign value of type 'Self' to type '_?'