Tengo un protocolo P que devuelve una copia del objeto:
protocol P {
func copy() -> Self
}
y una clase C que implementa P:
class C : P {
func copy() -> Self {
return C()
}
}
Sin embargo, si pongo el valor de retorno, Self
obtengo el siguiente error:
No se puede convertir la expresión de retorno del tipo 'C' al tipo de retorno 'Self'
También intenté regresar C
.
class C : P {
func copy() -> C {
return C()
}
}
Eso resultó en el siguiente error:
El método 'copy ()' en la clase no final 'C' debe volver
Self
a ajustarse al protocolo 'P'
Obras nada, excepto para el caso en que prefijo class C
con final
esto es hacer:
final class C : P {
func copy() -> C {
return C()
}
}
Sin embargo, si quiero una subclase C, nada funcionaría. ¿Hay alguna forma de evitar esto?
swift
protocols
subclassing
swift-protocols
aeubanks
fuente
fuente
class
sea afinal class
[[[self class] alloc] init]
. Entonces, supongo que la pregunta es: ¿existe una forma segura de llamar a la clase actual y llamar a un método init?Respuestas:
El problema es que está haciendo una promesa que el compilador no puede demostrar que cumplirá.
Entonces creaste esta promesa: la llamada
copy()
devolverá su propio tipo, completamente inicializado.Pero luego lo implementó de
copy()
esta manera:func copy() -> Self { return C() }
Ahora soy una subclase que no se anula
copy()
. Y devuelvo aC
, no completamente inicializadoSelf
(lo que prometí). Entonces eso no es bueno. Qué tal si:func copy() -> Self { return Self() }
Bueno, eso no se compilará, pero incluso si lo hiciera, no sería bueno. La subclase puede no tener un constructor trivial, por lo que
D()
puede que ni siquiera sea legal. (Aunque vea más abajo).OK, bueno, ¿qué tal:
func copy() -> C { return C() }
Sí, pero eso no regresa
Self
. VuelveC
. Aún no estás cumpliendo tu promesa."¡Pero ObjC puede hacerlo!" Especie de. Sobre todo porque no le importa si mantienes tu promesa como lo hace Swift. Si no logra implementar
copyWithZone:
en la subclase, es posible que no pueda inicializar completamente su objeto. El compilador ni siquiera le advertirá que ha hecho eso."Pero casi todo en ObjC se puede traducir a Swift, y ObjC sí
NSCopying
". Sí, así es como se define:func copy() -> AnyObject!
Entonces puedes hacer lo mismo (¡no hay razón para el! Aquí):
protocol Copyable { func copy() -> AnyObject }
Eso dice "No prometo nada sobre lo que recibes". También podrías decir:
protocol Copyable { func copy() -> Copyable }
Esa es una promesa que puedes hacer.
Pero podemos pensar en C ++ por un momento y recordar que hay una promesa que podemos hacer. Podemos prometer que nosotros y todas nuestras subclases implementaremos tipos específicos de inicializadores, y Swift lo hará cumplir (y así puede demostrar que estamos diciendo la verdad):
protocol Copyable { init(copy: Self) } class C : Copyable { required init(copy: C) { // Perform your copying here. } }
Y así es como debes realizar copias.
Podemos dar un paso más, pero se usa
dynamicType
, y no lo he probado exhaustivamente para asegurarnos de que siempre sea lo que queremos, pero debería ser correcto: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. } }
Aquí prometemos que hay un inicializador que realiza copias por nosotros, y luego podemos en tiempo de ejecución determinar cuál llamar, dándonos la sintaxis del método que estabas buscando.
fuente
func copy() -> C
funcionó en versiones beta anteriores, y fue consistente porque la conformidad del protocolo no fue heredada. (Ahora parece que la conformidad del protocolo se hereda yfunc copy() -> C
no funciona).init(copy: C)
lugarinit(copy: Self)
:(Self
pero el inicializador debe aceptar una variable escrita de forma estática,C
es decir, no es una gran mejora regresarAnyObject
en primer lugar.self.dynamicType.init( ... )
Con Swift 2, podemos usar extensiones de protocolo para esto.
protocol Copyable { init(copy:Self) } extension Copyable { func copy() -> Self { return Self.init(copy: self) } }
fuente
return Self(copy: self)
(al menos en Swift 2.2).Hay otra forma de hacer lo que quiere que implica aprovechar el tipo asociado de Swift. He aquí un ejemplo sencillo:
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()
fuente
En realidad, hay un truco que permite regresar fácilmente
Self
cuando lo requiera un protocolo ( gist ):/// 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
fuente
return Vehicle() as! Self
foo()
no se aplica la anulación de , cadaVehicle
descendiente sinfoo()
una implementación personalizada producirá un bloqueo obvio enautocast()
. Por ejemplo:class SuperCar: Vehicle { } let superCar = SuperCar.foo()
. La instancia deVehicle
no se puede reducir aSuperCar
, por lo que forzar el desenvolvimiento de nil en 'autocast ()' conduce al bloqueo.foo()
. El único requisito es que la claseFoo
debe tener un inicializador requerido para que esto funcione como se muestra a continuación.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())! }*/ }
Siguiendo la sugerencia de Rob, esto podría hacerse más genérico con los tipos asociados . Cambié un poco el ejemplo para demostrar los beneficios del enfoque.
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) } }
fuente
Tuve un problema similar y se me ocurrió algo que puede ser útil, así que pensé en compartirlo para referencia futura porque este es uno de los primeros lugares que encontré al buscar una solución.
Como se indicó anteriormente, el problema es la ambigüedad del tipo de retorno para la función copy (). Esto se puede ilustrar muy claramente separando las funciones copy () -> C y copy () -> P:
Entonces, asumiendo que define el protocolo y la clase de la siguiente manera:
protocol P { func copy() -> P } class C:P { func doCopy() -> C { return C() } func copy() -> C { return doCopy() } func copy() -> P { return doCopy() } }
Esto compila y produce los resultados esperados cuando el tipo de valor de retorno es explícito. Cada vez que el compilador tiene que decidir cuál debe ser el tipo de retorno (por sí solo), encontrará la situación ambigua y fallará para todas las clases concretas que implementan el protocolo P.
Por ejemplo:
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
En conclusión, esto funcionaría en situaciones en las que no estás usando la función copy () de la clase base o siempre tienes un contexto de tipo explícito.
Descubrí que usar el mismo nombre de función que la clase concreta generaba un código difícil de manejar en todas partes, así que terminé usando un nombre diferente para la función copy () del protocolo.
El resultado final es más parecido a:
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() } }
Por supuesto, mi contexto y funciones son completamente diferentes, pero en el espíritu de la pregunta, traté de mantenerme lo más cerca posible del ejemplo dado.
fuente
Swift 5.1 ahora permite un lanzamiento forzado a uno mismo,
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
fuente
Solo tirando mi sombrero al ring aquí. Necesitábamos un protocolo que devolviera un opcional del tipo en el que se aplicó el protocolo. También queríamos que la anulación devolviera explícitamente el tipo, no solo Self.
El truco es en lugar de usar 'Self' como el tipo de retorno, en su lugar define un tipo asociado que establece igual a Self, luego usa ese tipo asociado.
Aquí está la forma antigua, usando Self ...
protocol Mappable{ static func map() -> Self? } // Generated from Fix-it extension SomeSpecificClass : Mappable{ static func map() -> Self? { ... } }
Aquí está la nueva forma de usar el tipo asociado. Tenga en cuenta que el tipo de retorno es explícito ahora, no 'Self'.
protocol Mappable{ associatedtype ExplicitSelf = Self static func map() -> ExplicitSelf? } // Generated from Fix-it extension SomeSpecificClass : Mappable{ static func map() -> SomeSpecificClass? { ... } }
fuente
Para agregar a las respuestas con el
associatedtype
camino, sugiero mover la creación de la instancia a una implementación predeterminada de la extensión del protocolo. De esa manera, las clases conformes no tendrán que implementarlo, evitando así la duplicación de código: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()
fuente