Let's start with:
/// A type where an instance may be an independent copy of another instance,
/// with various levels of copying for sub-objects.
protocol Cloner {
/// Creates a copy of the given instance, where sub-objects are themselves
/// cloned to the given depth.
init?(copying original: Self, toDepth depth: Int)
}
And I make a sample class that conforms to Cloner:
class Sample1: Cloner {
init() {}
required convenience init?(copying original: Sample1, toDepth depth: Int) {
self.init()
}
}
Where I can choose whether the initializer copied from Cloner is convenience or not.
The oddity is when I make a derived class:
class Sample2: Sample1 {
var value: Int
init(value: Int) {
self.value = value
super.init()
}
required convenience init?(copying original: Sample1, toDepth depth: Int) {
guard let original2 = original as? Sample2 else { return nil }
self.init(value: original2.value)
}
}
Note that the first parameter is of the Sample1 type, not Sample2! In fact, if I make one that matches Sample2 for the sake of Cloner, the compiler insists on making the Sample1 variant above.
I was really stumped. Until I made a separate derived class Sample3 from Sample1, then made another one, Sample4, that derived from Sample3 instead. Neither Sample3 nor Sample4 had any members; so they inherited the ones from Sample1. When I created some sample objects in the playground, I was surprised as the suggestions.
For Sample1, I got suggestions for the empty and copying initializers. For Sample2, I got the single-Int initializer, the copying initializer with Sample1, and then an additional copying initializer with Sample2 instead. Analogous results happened when creating Sample3 and Sample4 objects. (With Sample4, only the Sample1 and Sample4 versions of the copying initializer appeared.). So the rule seems to be that for class types, the first class to incorporate a protocol with initializer requirements involving Self gets the parameter type named after itself. Derived classes must still use the first base class in the type signature. However, the function suggestions will create two versions of the initializer, one using the base class for the parameter and one with the current class for the parameter.
Am I guessing right? Why was it done this way? Why can't I fill in Self with the actual current class and have it count? My example uses a fail-able initializer, so I have a policy in place if the Sample1 parameter gets an argument of a more derived type. But what are derived classes supposed to do when the initializer is non-fail-able and but the base-class parameter is called with a different class object? (For instance, what if my initializer was non-fail-able and I created a Sample2 object, but filled in the Sample1 parameter with a Sample4 argument?)