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?)