Mam protokół P, który zwraca kopię obiektu:
protocol P {
func copy() -> Self
}
i klasa C, która implementuje P:
class C : P {
func copy() -> Self {
return C()
}
}
Jednak czy umieszczę wartość zwracaną, gdy Self
otrzymam następujący błąd:
Nie można przekonwertować wyrażenia zwrotnego typu „C” na zwrot typu „Self”
Próbowałem też wrócić C
.
class C : P {
func copy() -> C {
return C()
}
}
Spowodowało to następujący błąd:
Metoda „copy ()” w nie ostatecznej klasie „C” musi powrócić
Self
do zgodności z protokołem „P”
Nic nie działa z wyjątkiem przypadku, w którym przedrostek class C
z final
ie do:
final class C : P {
func copy() -> C {
return C()
}
}
Jednak jeśli chcę podklasę C, nic nie zadziała. Czy można to obejść?
swift
protocols
subclassing
swift-protocols
aeubanks
źródło
źródło
class
jest to afinal class
[[[self class] alloc] init]
. Więc myślę, że pytanie brzmi, czy istnieje bezpieczny dla typu sposób wywołania bieżącej klasy i wywołania metody init?Odpowiedzi:
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 zwracamC
, nie w pełni zainicjalizowanySelf
(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
. WracaC
. 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) { // Perform your copying here. } }
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) { // Perform your copying here. } }
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.
źródło
func copy() -> C
działało w poprzednich wersjach beta i było spójne, ponieważ zgodność z protokołem nie była dziedziczona. (Teraz wydaje się, że zgodność protokołu jest dziedziczona ifunc copy() -> C
nie działa.)init(copy: C)
zamiast tego są one wymaganeinit(copy: Self)
:(Self
ale inicjator musi wtedy zaakceptować zmienną wpisaną statycznie,C
co oznacza, że po prostu zwracanie nie jest zbytnim ulepszeniemAnyObject
.self.dynamicType.init( ... )
W Swift 2 możemy do tego użyć rozszerzeń protokołów.
protocol Copyable { init(copy:Self) } extension Copyable { func copy() -> Self { return Self.init(copy: self) } }
źródło
return Self(copy: self)
(przynajmniej w Swift 2.2).Jest inny sposób robienia tego, co chcesz, polegający na wykorzystaniu skojarzonego z nim typu języka Swift. Oto prosty przykład:
public protocol Creatable { associatedtype ObjectType = Self static func create() -> ObjectType } class MyClass { // Your class stuff here } extension MyClass: Creatable { // Define the protocol function to return class type static func create() -> MyClass { // Create an instance of your class however you want return MyClass() } } let obj = MyClass.create()
źródło
Właściwie istnieje sztuczka, która pozwala łatwo wrócić,
Self
gdy wymaga tego protokół ( streszczenie ):/// Cast the argument to the infered function return type. func autocast<T>(some: Any) -> T? { return some as? T } protocol Foo { static func foo() -> Self } class Vehicle: Foo { class func foo() -> Self { return autocast(Vehicle())! } } class Tractor: Vehicle { override class func foo() -> Self { return autocast(Tractor())! } } func typeName(some: Any) -> String { return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)" } let vehicle = Vehicle.foo() let tractor = Tractor.foo() print(typeName(vehicle)) // Vehicle print(typeName(tractor)) // Tractor
źródło
return Vehicle() as! Self
foo()
nie jest wymuszane, każdyVehicle
element podrzędny bezfoo()
niestandardowej implementacji spowoduje oczywistą awarięautocast()
. Na przykład:class SuperCar: Vehicle { } let superCar = SuperCar.foo()
. InstancjiVehicle
nie można sprowadzić doSuperCar
- więc wymuś rozpakowanie nil w „autocast ()” prowadzi do awarii.foo()
. Jedynym wymaganiem jest to, że klasaFoo
musi mieć wymagany inicjator, aby to działało, jak pokazano poniżej.class Vehicle: Foo { public required init() { // Some init code here } class func foo() -> Self { return autocast(self.init())! // return autocast(Vehicle())! } } class Tractor: Vehicle { //Override is not necessary /*override class func foo() -> Self { return autocast(Tractor())! }*/ }
Zgodnie z sugestią Roba można to uczynić bardziej ogólnym z powiązanymi typami . Zmieniłem nieco przykład, aby pokazać korzyści płynące z tego podejścia.
protocol Copyable: NSCopying { associatedtype Prototype init(copy: Prototype) init(deepCopy: Prototype) } class C : Copyable { typealias Prototype = C // <-- requires adding this line to classes required init(copy: Prototype) { // Perform your copying here. } required init(deepCopy: Prototype) { // Perform your deep copying here. } @objc func copyWithZone(zone: NSZone) -> AnyObject { return Prototype(copy: self) } }
źródło
Miałem podobny problem i wymyśliłem coś, co może być przydatne, więc pomyślałem, że podzielę się tym na przyszłość, ponieważ jest to jedno z pierwszych miejsc, które znalazłem, szukając rozwiązania.
Jak wspomniano powyżej, problemem jest niejednoznaczność zwracanego typu funkcji copy (). Można to bardzo wyraźnie zilustrować, oddzielając funkcje copy () -> C i copy () -> P:
Więc zakładając, że zdefiniujesz protokół i klasę w następujący sposób:
protocol P { func copy() -> P } class C:P { func doCopy() -> C { return C() } func copy() -> C { return doCopy() } func copy() -> P { return doCopy() } }
To kompiluje i daje oczekiwane wyniki, gdy typ wartości zwracanej jest jawny. Za każdym razem, gdy kompilator musi samodzielnie zdecydować, jaki powinien być zwracany typ (samodzielnie), uzna sytuację za niejednoznaczną i zakończy się niepowodzeniem dla wszystkich klas konkretnych implementujących protokół P.
Na przykład:
var aC:C = C() // aC is of type C var aP:P = aC // aP is of type P (contains an instance of C) var bC:C // this to test assignment to a C type variable var bP:P // " " " P " " bC = aC.copy() // OK copy()->C is used bP = aC.copy() // Ambiguous. // compiler could use either functions bP = (aC as P).copy() // but this resolves the ambiguity. bC = aP.copy() // Fails, obvious type incompatibility bP = aP.copy() // OK copy()->P is used
Podsumowując, zadziałałoby to w sytuacjach, w których albo nie używasz funkcji copy () klasy bazowej, albo zawsze masz jawny kontekst typu.
Zauważyłem, że używając tej samej nazwy funkcji, co klasa konkretna stworzona dla nieporęcznego kodu wszędzie, więc ostatecznie użyłem innej nazwy dla funkcji copy () protokołu.
Efekt końcowy jest bardziej podobny do:
protocol P { func copyAsP() -> P } class C:P { func copy() -> C { // there usually is a lot more code around here... return C() } func copyAsP() -> P { return copy() } }
Oczywiście mój kontekst i funkcje są zupełnie inne, ale w duchu pytania starałem się trzymać jak najbliżej podanego przykładu.
źródło
Swift 5.1 pozwala teraz na wymuszone rzucenie na siebie,
as! Self
1> protocol P { 2. func id() -> Self 3. } 9> class D : P { 10. func id() -> Self { 11. return D() 12. } 13. } error: repl.swift:11:16: error: cannot convert return expression of type 'D' to return type 'Self' return D() ^~~ as! Self 9> class D : P { 10. func id() -> Self { 11. return D() as! Self 12. } 13. } //works
źródło
Tylko wrzucam swój kapelusz do ringu. Potrzebowaliśmy protokołu, który zwrócił opcjonalny typ, na którym zastosowano protokół. Chcieliśmy również, aby przesłonięcie jawnie zwracało typ, a nie tylko Self.
Sztuczka polega na tym, że zamiast używać „Self” jako typu zwracanego, zamiast tego należy zdefiniować powiązany typ, który ustawiasz jako równy Self, a następnie użyć tego skojarzonego typu.
Oto stary sposób, używając Self ...
protocol Mappable{ static func map() -> Self? } // Generated from Fix-it extension SomeSpecificClass : Mappable{ static func map() -> Self? { ... } }
Oto nowy sposób korzystania z powiązanego typu. Zwróć uwagę, że zwracany typ jest teraz jawny, a nie „Self”.
protocol Mappable{ associatedtype ExplicitSelf = Self static func map() -> ExplicitSelf? } // Generated from Fix-it extension SomeSpecificClass : Mappable{ static func map() -> SomeSpecificClass? { ... } }
źródło
Aby dodać do odpowiedzi
associatedtype
sposób, proponuję przenieść tworzenie instancji na domyślną implementację rozszerzenia protokołu. W ten sposób klasy zgodne nie będą musiały go implementować, oszczędzając w ten sposób duplikacji kodu:protocol Initializable { init() } protocol Creatable: Initializable { associatedtype Object: Initializable = Self static func newInstance() -> Object } extension Creatable { static func newInstance() -> Object { return Object() } } class MyClass: Creatable { required init() {} } class MyOtherClass: Creatable { required init() {} } // Any class (struct, etc.) conforming to Creatable // can create new instances without having to implement newInstance() let instance1 = MyClass.newInstance() let instance2 = MyOtherClass.newInstance()
źródło