the - swift(lenguaje de programación)
Protocolo func regresando Self (9)
¡Swift 5.1 ahora permite un lanzamiento forzado a Self,
as! 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
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 como
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é devolver
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 devolver
Self
para cumplir con el protocolo ''P''
Nada funciona, excepto el caso donde prefijo la
class C
con
final
es decir, hacer:
final class C : P {
func copy() -> C {
return C()
}
}
Sin embargo, si quiero subclase C entonces nada funcionaría. ¿Hay alguna forma de evitar esto?
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)
}
}
El problema es que está haciendo una promesa de que el compilador no puede demostrar que cumplirá.
Entonces creaste esta promesa: Llamar a
copy()
devolverá su propio tipo, completamente inicializado.
Pero luego implementó
copy()
esta manera:
func copy() -> Self {
return C()
}
Ahora soy una subclase que no anula
copy()
.
Y devuelvo una
C
, no un
Self
totalmente inicializado (que prometí).
Entonces eso no es bueno.
Qué tal si:
func copy() -> Self {
return Self()
}
Bueno, eso no compilará, pero incluso si lo hiciera, no sería bueno.
La subclase puede no tener un constructor trivial, por lo que
D()
podría no ser legal.
(Aunque ver más abajo).
Bien, ¿qué tal si:
func copy() -> C {
return C()
}
Sí, pero eso no devuelve el
Self
.
Devuelve
C
Todavía no estás cumpliendo tu promesa.
"¡Pero ObjC puede hacerlo!"
Especie de.
Principalmente porque no le importa si cumple su promesa como lo hace Swift.
Si no puede implementar
copyWithZone:
en la subclase, es posible que no pueda inicializar completamente su objeto.
El compilador ni siquiera te advertirá que lo has hecho.
"Pero casi todo en ObjC se puede traducir a Swift, y ObjC tiene
NSCopying
".
Sí, y así es como se define:
func copy() -> AnyObject!
Entonces puedes hacer lo mismo (¡no hay razón para esto!):
protocol Copyable {
func copy() -> AnyObject
}
Eso dice "No prometo nada sobre lo que recuperas". También podrías decir:
protocol Copyable {
func copy() -> Copyable
}
Esa es una promesa que puedes hacer.
Pero podemos pensar en C ++ por un tiempo 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
dynamicType
un paso más allá, pero utiliza
dynamicType
, y no lo he probado exhaustivamente para asegurarme 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 para nosotros, y luego podemos determinar en el tiempo de ejecución a cuál llamar, dándonos la sintaxis del método que estaba buscando.
En realidad, hay un truco que permite devolver
fácilmente
Self
cuando lo requiere 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
Hay otra forma de hacer lo que quiera que implica aprovechar el tipo asociado de Swift. Aquí hay un ejemplo simple:
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()
Para agregar a las respuestas con la forma de tipo
associatedtype
, sugiero mover la creación de la instancia a una implementación predeterminada de la extensión de 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()
Siguiendo la sugerencia de Rob, esto podría hacerse más genérico con los tipos asociados . He cambiado 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)
}
}
Solo tiro 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 vieja manera, 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? {
...
}
}
Tuve un problema similar y se me ocurrió algo que podría 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, suponiendo que defina 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 del 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 usted no utiliza la función copy () de la clase base o siempre tiene un contexto de tipo explícito.
Descubrí que usar el mismo nombre de función que la clase concreta creaba 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 como:
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.