We are not allowed to use circular associated type requirements.
protocol ParentPro : class {
associatedtype Child : ChildPro
var children:[Child] { get set }
}
protocol ChildPro {
associatedtype Parent : ParentPro
weak var parent:Parent? { get set }
}
In this example, the
ParentPro
protocol requires something conforming to the
ChildPro
protocol, and
ChildPro
something conforming to
ParentPro
. This kind of circular dependency is not allowed. Instead, reorganize the requirements into a hierarchy.
In this case, that means splitting the full type of the parent, from the definition of the child:
protocol ParentPro : class {
associatedtype Child : ChildPro
var children:[Child] { get set }
}
protocol ChildPro : Leaf {
associatedtype Parent : AnyObject
weak var parent:Parent? { get set }
}
The definition of the
Parent
associated type has only the information it needs in the immediate context: that's it's a
class
, so the
parent
property can be
weak
. We did this by making it conform to
AnyObject
.
Now, we create concrete types.
class BookShelf : ParentPro {
var children: [Book] = []
}
struct Book : ChildPro {
weak var parent: BookShelf?
}
var shelf:BookShelf = BookShelf()
var book = Book()
The concrete type
BookShelf
conforms the
ParentPro
protocol, while the
Book
conforms to the
ChildPro
.
What would really be nice, is if there was a method on the Parent, which could set the parent reference on the child when it's added to the Parent. We can't write the method on
ParentPro
directly, because without the circular constraint, the full type of the child's parent type is not known.
extension ParentPro where ChildType.Parent == Self {
func add(child:ChildType) {
var theChild = child
theChild.parent = self
children.append(theChild)
}
}
We've added an extension on ParentPro, which adds the
add(child:
method, but the
where
clause only allows the method to exist when the child type's parent type is the type of the container it's getting added to. Thus, making the call to
theChild.parent = self
type-safe.
class HappyMeal : ParentPro {
var children:[Nugget]
}
struct Nugget : ChildPro {
weak var parent:HappyMeal?
}
Since these are protocols, and not concrete generics, we are free to create totally independent types which gain these behaviors.
var happyMeal = HappyMeal()
happyMeal.add(Nugget())
Merely by declaring conforming types, the type-safe extension now works on the
HappyMeal
as well.
Comments for No Circular Requirements