Problem polega na tym, że obiecujesz, że kompilator nie może udowodnić, że dotrzymasz.
Więc stworzyłeś tę obietnicę: wywołanie copy()
zwróci swój własny typ, w pełni zainicjowany.
Ale potem wdrożyłeś w copy()
ten sposób:
func copy() -> Self {
return C()
}
Teraz jestem podklasą, która nie zastępuje copy()
. I zwracam C
, nie w pełni zainicjalizowany Self
(co obiecałem). Więc to nie jest dobre. Co powiesz na:
func copy() -> Self {
return Self()
}
Cóż, to się nie skompiluje, ale nawet gdyby tak się stało, nie byłoby dobrze. Podklasa może nie mieć trywialnego konstruktora, więc D()
może nawet nie być legalna. (Chociaż patrz poniżej.)
OK, a co powiesz na:
func copy() -> C {
return C()
}
Tak, ale to nie wraca Self
. Wraca C
. Nadal nie dotrzymujesz obietnicy.
"Ale ObjC może to zrobić!" Cóż, w pewnym sensie. Głównie dlatego, że nie obchodzi go, czy dotrzymasz obietnicy tak, jak robi to Swift. Jeśli nie uda ci się zaimplementować copyWithZone:
w podklasie, możesz nie zainicjować pełnego obiektu. Kompilator nawet nie ostrzeże Cię, że to zrobiłeś.
„Ale prawie wszystko w ObjC można przetłumaczyć na Swift, a ObjC tak NSCopying
”. Tak, i oto jak to zdefiniowano:
func copy() -> AnyObject!
Możesz więc zrobić to samo (nie ma powodu! Tutaj):
protocol Copyable {
func copy() -> AnyObject
}
To mówi: „Nie obiecuję niczego, co otrzymasz z powrotem”. Możesz też powiedzieć:
protocol Copyable {
func copy() -> Copyable
}
To obietnica, którą możesz złożyć.
Ale możemy myśleć o C ++ na chwilę i pamiętać, że jest to obietnica, że można zrobić. Możemy obiecać, że my i wszystkie nasze podklasy zaimplementujemy określone rodzaje inicjatorów, a Swift wymusi to (i może udowodnić, że mówimy prawdę):
protocol Copyable {
init(copy: Self)
}
class C : Copyable {
required init(copy: C) {
}
}
I tak należy wykonywać kopie.
Możemy pójść o krok dalej, ale używa go dynamicType
i nie testowałem go szczegółowo, aby upewnić się, że zawsze jest to, czego chcemy, ale powinno być poprawne:
protocol Copyable {
func copy() -> Self
init(copy: Self)
}
class C : Copyable {
func copy() -> Self {
return self.dynamicType(copy: self)
}
required init(copy: C) {
}
}
Tutaj obiecujemy, że istnieje inicjator, który wykonuje dla nas kopie, a następnie możemy w czasie wykonywania określić, który z nich wywołać, podając nam składnię metody, której szukasz.