UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Polimorfismo y cambio de tipos

Debido a que las clases pueden heredar de otras (por ejemplo, CountrySinger puede heredar de Singer) significa que una clase es un superconjuto de otra: la clase B tiene todas las cosas que tiene A, con algunos extras. Esto, a su vez, significa que puedes tratar B como tipo B o como tipo A, dependiendo de tus necesidades.

¿Confundido? Vamos a probar algo de código:

class Album {
    var name: String

    init(name: String) {
        self.name = name
    }
}

class StudioAlbum: Album {
    var studio: String

    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }
}

class LiveAlbum: Album {
    var location: String

    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }
}

Esto define tres clases: albums, studio albums y live albums, con los dos últimos heredando de Album. Debido a que cualquier instancia de LiveAlbum es heredada de Album, puedes tratarlo como Album o LiveAlbum, ambos al mismo tiempo. Esto es llamado "polimorfismo", pero significa que puedes escribir código como este:

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

Creamos un arreglo que solo contiene albums, pero dentro ponemos dos studio albums y un live album. Esto está perfecto en Swift porque descienden de la clase Album, por lo que comparten el mismo comportamiento básico.

Podemos incluso llevarlo más allá para demostrar realmente como funciona el polimorfismo. Vamos a añadir un método getPerformance() a las tres clases:

class Album {
    var name: String

    init(name: String) {
        self.name = name
    }

    func getPerformance() -> String {
        return "The album \(name) sold lots"
    }
}

class StudioAlbum: Album {
    var studio: String

    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }

    override func getPerformance() -> String {
        return "The studio album \(name) sold lots"
    }
}

class LiveAlbum: Album {
    var location: String

    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }

    override func getPerformance() -> String {
        return "The live album \(name) sold lots"
    }
}

El método getPerformance() existe en la clase Album, pero ambas clases hijas lo sobreescriben. Cuando creamos un arreglo que contiene Albums, realmente lo estamos rellenando con subclases de albums: LiveAlbum y StudioAlbum. Estos entran perfectamente dentro del arreglo ya que heredan de la clase album, pero nunca pierden su clase original. Luego podríamos escribir código como este:

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

for album in allAlbums {
    print(album.getPerformance())
}

Eso automáticamente utilizará la versión sobreescrita de getPerformance() dependiendo de la subclase en cuestión. Esto es polimorfismo en acción: un objeto puede trabajar como su clase y sus clases padre, todo al mismo tiempo.

Convirtiendo tipos

A menudo encontrarás que tienes un objeto de un tipo concreto, pero en realidad sabes que es un tipo diferente. Lamentablemente, si Swift no sabe lo que tu sabes, no va a poder construir tu código. Entonces, una solución se llama conversión de tipos: convertir un objeto de un tipo a otro.

Es probable que estes pensando por qué esto es necesario, pero puedo darte un ejemplo muy simple:

for album in allAlbums {
    print(album.getPerformance())
}

Ese fue nuestro bucle hace unos minutos. El arreglo allAlbums contiene el tipo Album, pero no sabemos si en realidad contiene una de las subclases: StudioAlbum o LiveAlbum. Swift no lo sabe, así que si intentas escribir algo como print(album.studio) no lo hará debido a que solo los objetos de StudioAlbum tienen esa propiedad.

La conversión de tipos en Swift viene de tres maneras, pero la mayor parte de las veces solo encontrarás dos: as? y as!, conocidas como conversion opcional y conversión forzada. El primero significa "pienso que esta conversión puede ser cierta, pero puede fallar", y la segunda significa "se que esta conversión es cierta, y estoy feliz de que mi aplicación se cierre inesperadamente si me equivoco".

Nota: cuando digo "conversión" no quiero decir que mi objecto se transforme literalmente. En su lugar, solo se convierte la forma en la que Swift trata el objeto, le estas diciendo a Swift que un objeto que pensaba que era de tipo A, es en realidad de tipo E.

Los signos de pregunta y exclamación deberían darte una pista de lo que esta ocurriendo, porque es muy similar al territorio opcional. Por ejemplo, si escribes esto:

for album in allAlbums {
    let studioAlbum = album as? StudioAlbum
}

Swift hará que studioAlbum tenga el tipo de datos StudioAlbum?. Es decir, un álbum de estudio opcional: la conversión puede haber funcionado, en cuyo caso tienes un álbum de estudio con el que puedes trabajar; o puede haber fallado, en cuyo caso tienes un nil.

Esto se utiliza más comúnmente con if let para automáticamente quitar el resultado opcional, como aquí:

for album in allAlbums {
    print(album.getPerformance())

    if let studioAlbum = album as? StudioAlbum {
        print(studioAlbum.studio)
    } else if let liveAlbum = album as? LiveAlbum {
        print(liveAlbum.location)
    }
}

Esto pasará por cada álbum e imprimirá los detalles de ventas, porque es común a la clase Album y a todas sus subclases. Luego comprueba si puede convertir el valor album en un StudioAlbum, y si es posible, imprime el nombre del estudio. Lo mismo se hace para los LiveAlbum que contiene el arreglo.

Forzar la conversión se hace solo cuando estás totalmente seguro de que un objeto de un tipo puede tratarse como otro diferente, pero si estás equivocado tu programa dejará de funcionar. Forzar la conversión no necesita devolver un valor opcional, porque estas diciendo que la conversión sí va a funcionar, si te equivocas, significa que escribiste mal el código.

Para demostrar esto de forma que no falle, eliminemos el LiveAlbum de forma que solo tengamos StudioAlbum en el arreglo:

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")

var allAlbums: [Album] = [taylorSwift, fearless]

for album in allAlbums {
    let studioAlbum = album as! StudioAlbum
    print(studioAlbum.studio)
}

Obviamente es un ejemplo premeditado, porque si fuera tu código real cambiarías tan solo allAlbums para que contuviese el tipo de dato [StudioAlbum]. Aún así, muestra como funciona el forzar la conversión, y un ejemplo de que no fallará porque has hecho las suposiciones correctas.

Swift te permite convertir la parte del bucle del arreglo, que en este caso concreto sería más eficiente. Si quisieras escribir la conversión forzada a nivel del arreglo, escribirías esto:

for album in allAlbums as! [StudioAlbum] {
    print(album.studio)
}

Eso no necesita ya convertir cada elemento de dentro del bucle, porque eso ocurre cuando el bucle comienza. Una vez más, es mejor que tengas razón en que todos los elementos del arreglo son StudioAlbum ya que en caso contrario tu código fallará.

Swift también permite la conversión opcional a nivel de arreglo, aunque es un poco más complicado ya que necesita usar el operador de unión a nil para asegurarse de que haya algún valor en el bucle. Aquí un ejemplo:

for album in allAlbums as? [LiveAlbum] ?? [LiveAlbum]() {
    print(album.location)
}

Lo que significa es que "intenta convertir allAlbums en un arreglo de objetos LiveAlbum, pero si falla tan solo crea un arreglo vacio de LiveAlbum y usa ese en su lugar", es decir, no hace nada.

Convertir tipos comunes con constructores

La conversión de tipos es útil cuando sabes algo que Swift no lo sabe, por ejemplo, cuando tienes un objeto de tipo A y Swift cree que es de tipo B. Sin embargo, la conversión de tipos es útil solo cuando esos tipos en realidad son lo que dices, no puedes forzar un tipo A a un tipo Z si no están relacionados.

Por ejemplo, si tienes un entero llamado number, no puedes escribir código como este para convertirlo en una cadena de texto:

let number = 5
let text = number as! String

Es decir, no puedes forzar un entero a una cadena de texto, porque son dos tipos completamente diferentes. En su lugar, necesitas crear una nueva cadena de texto dándola el entero, y Swift sabe cómo convertirlo. La diferencia es sutil: este es un nuevo valor, en lugar de ser una reinterpretación del mismo valor.

Luego el código debe reescribirse así:

let number = 5
let text = String(number)
print(text)

Esto solo funciona para algunos tipos de datos incorporados en Swift: puedes convertir enteros y flotantes a cadenas de texto y viceversa, por ejemplo, pero si has creado dos estructuras personalizadas, Swift no puede mágicamente convertir una en otra, necesitas escribir este código tu mismo.

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.