Ich habe ein Protokoll P, das eine Kopie des Objekts zurückgibt:
protocol P {
func copy() -> Self
}
und eine Klasse C, die P implementiert:
class C : P {
func copy() -> Self {
return C()
}
}
Ob ich jedoch den Rückgabewert setze, Self
erhalte ich den folgenden Fehler:
Der Rückgabeausdruck vom Typ 'C' kann nicht in den Rückgabetyp 'Self' konvertiert werden.
Ich habe auch versucht zurückzukehren C
.
class C : P {
func copy() -> C {
return C()
}
}
Dies führte zu folgendem Fehler:
Die Methode 'copy ()' in der nicht endgültigen Klasse 'C' muss zurückkehren
Self
, um dem Protokoll 'P' zu entsprechen.
Nichts funktioniert , außer für den Fall, dass ich das Präfix class C
mit final
also zB:
final class C : P {
func copy() -> C {
return C()
}
}
Wenn ich jedoch C unterordnen möchte, würde nichts funktionieren. Gibt es einen Weg daran vorbei?
swift
protocols
subclassing
swift-protocols
aeubanks
quelle
quelle
class
ist afinal class
[[[self class] alloc] init]
. Die Frage ist also, ob es eine typsichere Möglichkeit gibt, die aktuelle Klasse aufzurufen und eine init-Methode aufzurufen.Antworten:
Das Problem ist, dass Sie ein Versprechen abgeben, dass der Compiler nicht beweisen kann, dass Sie es halten werden.
Sie haben dieses Versprechen erstellt: Calling
copy()
gibt seinen eigenen Typ zurück, der vollständig initialisiert ist.Aber dann haben Sie Folgendes implementiert
copy()
:func copy() -> Self { return C() }
Jetzt bin ich eine Unterklasse, die nicht überschreibt
copy()
. Und ich gebe ein zurückC
, kein vollständig initialisiertesSelf
(was ich versprochen habe). Das ist also nicht gut. Wie wäre es mit:func copy() -> Self { return Self() }
Nun, das wird nicht kompiliert, aber selbst wenn es so wäre, wäre es nicht gut. Die Unterklasse hat möglicherweise keinen trivialen Konstruktor und ist daher
D()
möglicherweise nicht einmal legal. (Siehe unten.)OK, wie wäre es mit:
func copy() -> C { return C() }
Ja, aber das kommt nicht zurück
Self
. Es kehrt zurückC
. Du hältst dein Versprechen immer noch nicht."Aber ObjC kann es schaffen!" Naja, so ungefähr. Meistens, weil es egal ist, ob Sie Ihr Versprechen so halten wie Swift. Wenn Sie die Implementierung
copyWithZone:
in der Unterklasse nicht durchführen können, können Sie Ihr Objekt möglicherweise nicht vollständig initialisieren. Der Compiler warnt Sie nicht einmal davor, dass Sie das getan haben."Aber fast alles in ObjC kann in Swift übersetzt werden, und ObjC hat es
NSCopying
." Ja, und so wird es definiert:func copy() -> AnyObject!
Sie können also dasselbe tun (hier gibt es keinen Grund dafür!):
protocol Copyable { func copy() -> AnyObject }
Das heißt "Ich verspreche nichts darüber, was Sie zurückbekommen." Man könnte auch sagen:
protocol Copyable { func copy() -> Copyable }
Das ist ein Versprechen, das du machen kannst.
Aber wir können eine Weile über C ++ nachdenken und uns daran erinnern, dass wir ein Versprechen geben können . Wir können versprechen, dass wir und alle unsere Unterklassen bestimmte Arten von Initialisierern implementieren werden, und Swift wird dies durchsetzen (und so beweisen, dass wir die Wahrheit sagen):
protocol Copyable { init(copy: Self) } class C : Copyable { required init(copy: C) { // Perform your copying here. } }
Und so sollten Sie Kopien erstellen.
Wir können noch einen Schritt weiter gehen, aber es wird verwendet
dynamicType
, und ich habe es nicht ausführlich getestet, um sicherzustellen, dass dies immer das ist, was wir wollen, aber es sollte korrekt sein: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. } }
Hier versprechen wir, dass es einen Initialisierer gibt, der Kopien für uns ausführt, und dann können wir zur Laufzeit bestimmen, welcher aufgerufen werden soll, und uns die von Ihnen gesuchte Methodensyntax geben.
quelle
func copy() -> C
in früheren Betas funktioniert hat, und es war konsistent, weil die Protokollkonformität nicht vererbt wurde. (Jetzt scheint es, dass die Protokollkonformität vererbt wird undfunc copy() -> C
nicht funktioniert.)init(copy: C)
stattdessen implementiert werden müsseninit(copy: Self)
:(Self
aber der Initialisierer muss dann alle eine statisch typisierte Variable akzeptieren,C
das heißt, es ist keine große Verbesserung, nurAnyObject
erst zurückzukehren.self.dynamicType.init( ... )
Mit Swift 2 können wir hierfür Protokollerweiterungen verwenden.
protocol Copyable { init(copy:Self) } extension Copyable { func copy() -> Self { return Self.init(copy: self) } }
quelle
return Self(copy: self)
(zumindest in Swift 2.2).Es gibt eine andere Möglichkeit, das zu tun, was Sie möchten, indem Sie den zugehörigen Typ von Swift nutzen. Hier ist ein einfaches Beispiel:
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()
quelle
Tatsächlich gibt es einen Trick, mit dem Sie problemlos zurückkehren können,
Self
wenn dies für ein Protokoll erforderlich ist :/// 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
quelle
return Vehicle() as! Self
foo()
nicht erzwungen wird, führt jederVehicle
Nachkomme ohnefoo()
benutzerdefinierte Implementierung zu einem offensichtlichen Absturzautocast()
. Zum Beispiel:class SuperCar: Vehicle { } let superCar = SuperCar.foo()
. Die Instanz vonVehicle
kann nicht herabgestuft werdenSuperCar
- daher erzwingt das erzwungene Auspacken von Null in 'autocast ()' einen Absturz.foo()
. Die einzige Voraussetzung ist, dass die KlasseFoo
über einen erforderlichen Initialisierer verfügt, damit dies wie unten gezeigt funktioniert.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())! }*/ }
Auf Robs Vorschlag hin könnte dies mit den zugehörigen Typen allgemeiner gestaltet werden . Ich habe das Beispiel ein wenig geändert, um die Vorteile des Ansatzes zu demonstrieren.
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) } }
quelle
Ich hatte ein ähnliches Problem und habe mir etwas ausgedacht, das nützlich sein könnte. Ich dachte, ich würde es als zukünftige Referenz weitergeben, da dies einer der ersten Orte ist, die ich bei der Suche nach einer Lösung gefunden habe.
Wie oben erwähnt, liegt das Problem in der Mehrdeutigkeit des Rückgabetyps für die Funktion copy (). Dies kann sehr deutlich veranschaulicht werden, indem die Funktionen copy () -> C und copy () -> P getrennt werden:
Angenommen, Sie definieren das Protokoll und die Klasse wie folgt:
protocol P { func copy() -> P } class C:P { func doCopy() -> C { return C() } func copy() -> C { return doCopy() } func copy() -> P { return doCopy() } }
Dies kompiliert und erzeugt die erwarteten Ergebnisse, wenn der Typ des Rückgabewerts explizit ist. Jedes Mal, wenn der Compiler (allein) entscheiden muss, wie der Rückgabetyp lauten soll, wird die Situation nicht eindeutig und schlägt für alle konkreten Klassen fehl, die das P-Protokoll implementieren.
Zum Beispiel:
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
Zusammenfassend lässt sich sagen, dass dies in Situationen funktioniert, in denen Sie entweder die copy () - Funktion der Basisklasse nicht verwenden oder immer einen expliziten Typkontext haben.
Ich fand heraus, dass die Verwendung des gleichen Funktionsnamens wie die konkrete Klasse überall für unhandlichen Code gemacht wurde, und verwendete daher einen anderen Namen für die copy () - Funktion des Protokolls.
Das Endergebnis ist eher wie folgt:
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() } }
Natürlich sind mein Kontext und meine Funktionen völlig unterschiedlich, aber im Geiste der Frage habe ich versucht, dem gegebenen Beispiel so nahe wie möglich zu kommen.
quelle
Ich werfe hier nur meinen Hut in den Ring. Wir brauchten ein Protokoll, das ein optionales Protokoll des Typs zurückgab, auf den das Protokoll angewendet wurde. Wir wollten auch, dass die Überschreibung den Typ explizit zurückgibt, nicht nur Self.
Der Trick besteht darin, anstatt 'Self' als Rückgabetyp zu verwenden, stattdessen einen zugeordneten Typ zu definieren, den Sie auf Self setzen, und dann diesen zugeordneten Typ zu verwenden.
Hier ist der alte Weg, mit Selbst ...
protocol Mappable{ static func map() -> Self? } // Generated from Fix-it extension SomeSpecificClass : Mappable{ static func map() -> Self? { ... } }
Hier ist der neue Weg mit dem zugehörigen Typ. Beachten Sie, dass der Rückgabetyp jetzt explizit ist und nicht 'Self'.
protocol Mappable{ associatedtype ExplicitSelf = Self static func map() -> ExplicitSelf? } // Generated from Fix-it extension SomeSpecificClass : Mappable{ static func map() -> SomeSpecificClass? { ... } }
quelle
Um die Antworten auf diese
associatedtype
Weise zu ergänzen , schlage ich vor, die Erstellung der Instanz auf eine Standardimplementierung der Protokollerweiterung zu verschieben. Auf diese Weise müssen die konformen Klassen es nicht implementieren, wodurch Code-Duplikationen vermieden werden: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()
quelle
Swift 5.1 erlaubt jetzt eine erzwungene Besetzung von Self,
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
quelle