NEW: Join my free 100 Days of SwiftUI challenge today! >>

Clases

Swift tiene otra forma de construir tipos de datos complejos llamada clases. Estas son similares a las estructuras, pero tienen diferencias importantes, incluyendo:

  • No tienes un constructor automático para inicializar tus clases; debes crear el tuyo propio.
  • Puedes definir una nueva clase basada en otra clase, añadiendo cualquier cosa nueva que desees.
  • Cuando creas una instancia de una clase, el resultado se llama objeto. Si copias ese objeto, ambas copias apuntan a los mismos datos de forma predeterminada - si cambias una, la copia cambia también.

Estas tres son las grandes diferencias, así que las trataré antes de continuar.

Inicializando un objeto

Si queremos convertir nuestra estructura Person en una clase Person, Swift no nos dejará escribir esto:

class Person {
    var clothes: String
    var shoes: String
}

Esto es debido a que estamos declarando dos atributos para ser String, que si recuerdas deben tener un valor obligatoriamente. Esto funcionaba en la estructura porque Swift de forma automática genera un constructor para nosotros que nos obligaba a asignar un valor a ambos, pero esto no ocurre con las clases, luego Swift no puede asegurarse de que vayamos a dar los valores.

Estas son tres posibles soluciones: hacer los dos valores opcionales, darlos valores predeterminados, o crear nuestro propio constructor. La primera opción es "fea" ya que introduce opcionales en todo el código que no es necesario que estén. La segunda opción funciona, pero es poco eficiente a no ser que utilizases los valores predeterminados. Por tanto, la opción adecuada es la tercera: crear tu propio constructor.

Para hacer esto, crea un método dentro de la clase llamado init() que tome los dos parámetros por los que nos estamos preocupando:

class Person {
    var clothes: String
    var shoes: String

    init(clothes: String, shoes: String) {
        self.clothes = clothes
        self.shoes = shoes
    }
}

Hay dos cosas que podrían llamarte la atención en este código.

Primero, no tienes que escribir func antes del método init() ya que este es especial. Segundo, debido a que los nombres de los parámetros pasados son los mismos que los nombres de los atributos que queremos asignar, debemos utilizar self. para aclararlo - "la propiedad clothes de este objeto debe ser asignada con el valor del parámetro clothes que recibe". Puedes darles nombres únicos si quieres, es tu decisión.

Importante: Swift requiere que todos los atributos no opcionales tengan un valor al final del constructor, o antes de la llamada a cualquier otro método - lo que primero ocurra.

Herencia de clases

La segunda diferencia entre clases y estructuras es que las clases pueden construirse basandose en otras para generar grandes cosas, conocido como herencia de clases. Esta es una técnica muy extendida en Cocoa Touch, incluso en programas básicos, por lo que es algo a lo que debes enfrentarte.

Comencemos con algo simple: una clase Singer que tiene atributos, los cuales son nombre y edad. Como métodos, tendrá un simple constructor para inicializar los atributos, más un método sing() que imprime algunas palabras:

class Singer {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func sing() {
        print("La la la la")
    }
}

Ahora podemos crear una instancia de esa clase llamando al constructor, luego leemos sus propiedades y llamamos al método:

var taylor = Singer(name: "Taylor", age: 25)
taylor.name
taylor.age
taylor.sing()

Esa es nuestra clase básica, pero vamos a desarrollarla: quiero definir una clase CountrySinger que tenga todo lo que la clase Singer tiene, pero que cuando llame a sing() en ella quiero que imprima "Trucks, guitars, and liquos" en su lugar.

Por supuesto puedes copiar y pegar la original Singer en una nueva clase llamada ContrySinger pero no es la mejor opción y puede volverse caótico si haces cambios después en la clase Singer y olvidas copiarlos. En cambio, Swift tiene una solución más inteligente: podemos definir CountrySinger como basada en Singer y todos sus atributos y métodos serán obtenidos en la nueva clase:

class CountrySinger: Singer {

}

Los dos puntos son los que hacen la magia: significan que "CountrySinger extiende Singer". Ahora, esa nueva clase CountrySinger (llamada subclase) no añade nada por ahora a Singer (llamada clase padre, o superclase). Queremos que tenga su propio método sing(), pero en Swift necesitas aprender una nueva palabra clave: override. Significa "se que tienes este método ya implementado en mi clase padre, pero quiero cambiarlo en la subclase".

Tener la palabra override es muy útil, porque deja clara la intención que tienes. También permite a Swift comprobar tu código: si no usas override, Swift no te permitirá cambiar el método que obtienes de tu superclase, o si usas override y no hay nada para sobreescribir, Swift te señalará el error.

Luego, tenemos que utilizar override func, así:

class CountrySinger: Singer {
    override func sing() {
        print("Trucks, guitars, and liquor")
    }
}

Ahora modifiquemos la forma en la que el objeto taylor es creado:

var taylor = CountrySinger(name: "Taylor", age: 25)
taylor.sing()

Si cambias CountrySinger a solo Singer verás que aparecen diferentes mensajes en el panel de resultados.

Ahora, para hacer las cosas más complicadas, vamos a definir una nueva clase llamada HeavyMetalSinger. Pero esta vez vamos a almacenar una nueva propiedad llamada noiseLevel que define como de alto le gusta al cantante heavy cantar a su micrófono.

Esto genera un problema, y es necesario resolverlo de una forma particular:

  • Swift necesita que todos los atributos no opcionales tengan un valor.
  • Nuestra clase Singer no tiene un atributo noiseLevel.
  • Luego necesitamos crear un constructor personalizado para HeavyMetalSinger que acepte el nivel de ruido.
  • Este nuevo constructor también necesita conocer el name y age del cantante de heavy metal, por lo que podemos pasarlo a la superclase Singer.
  • Pasar los datos a la superclase se realiza con una llamada a un método, y no puedes hacer llamadas de método hasta que no des valor a todos los atributos.
  • Primero necesitamos asignar nuestra propiedad (noiseLevel) y luego pasar los otros parámetos a la superclase.

Puede sonar complicado, pero el código es muy sencillo. Aquí está la clase HeavyMetalSinger, completada con nuestro propio método sing():

class HeavyMetalSinger: Singer {
    var noiseLevel: Int

    init(name: String, age: Int, noiseLevel: Int) {
        self.noiseLevel = noiseLevel
        super.init(name: name, age: age)
    }

    override func sing() {
        print("Grrrrr rargh rargh rarrrrgh!")
    }
}

Observa cómo el constructor toma tres parámetros, luego llama a super.init() para pasar name y age a la superclase Singer, pero solo después de asignar valor a nuestro propio atributo. Verás que super se utiliza mucho cuando se trabaja con clases, y solamente significa "llama a un método de la clase de la que heredo". Normalmente se utiliza para significar "deja que mi clase padre haga todo lo que necesite y después ya haré lo que considere adicional".

La herencia de clases es un gran tema, por lo que no te preocupes si no lo tienes claro aún. Sin embargo, hay una cosa que debes conocer: la herencia de clase a menudo tiene muchos niveles. Por ejemplo, A puede heredar de B, y B puede heredar de C, y C puede heredar de D, y así sucesivamente. Esto permite construir una funcionalidad y reutilizarla en varias clases, ayudando a tener un código modular y fácil de enterder.

Trabajando con código de Objective-C

Si quieres que parte del sistema operativo de Apple llame al método de tu clase de Swift, debes marcarlo con un atributo especial: @objc. Es la abriviatura de "Objective-C", y el atributo marca de forma efectiva como disponible para ejecutarse con código del antiguo Objective-C, que es casi todo de iOS, macOS, watchOS, y tvOS. Por ejemplo, si pides al sistema que llame a tu método después de que haya transcurrido un segundo, deberás marcarlo con @objc.

Por ahora no te preocupes mucho sobre @objc, no solo lo explicaré en el contexto más adelante, sino que Xcode siempre te dirá cuando es necesario. Alternativamente, si no quieres usar @objc para métodos individuales, puedes poner @objcMembers antes de tu clase para automáticamente hacer todos tus métodos disponibles para Objective-C.

Valores vs Referencias

Cuando copias una estructura, todo se duplica, incluyendo todos los valores. Esto significa que cambiar una copia de una estructura no cambia el resto, son todas individuales. Con las clases, cada copia de un objeto apunta al objeto original, por lo que si cambiar uno, el resto también cambiará. Swift llama a las estructuras "tipos de valor" ya que tan solo apuntan a un valor, y a las clases "tipos de referencia" ya que todos los objetos son únicamente referencias compartidas al valor real.

Esta es una importante diferencia, y significa que elegir entre estructuras y clases es importante:

  • Si quieres tener un estado compartido que pase y modifique en un lugar, estás buscando clases. Puedes pasarlas a funciones o almacenarlas en arreglos, modificarlas allí, y que los cambios se vean reflejados en el resto del programa.
  • Si quieres evitar un estado compartido donde una copia no afecte al resto, estás buscando estructuras. Puedes pasarlas a funciones o almacenarlas en arreglos, modificarlas allí, y que los cambios no se vean reflejados en otros lugares donde se haga referencia.

Si tuviera que resumir la diferencia clave entre estructuras y clases, diría esto: las clases son más flexibles, mientras que las estructuras son más seguras. Como regla básica, siempre debes utilizar estructuras hasta que tengas una razón específica para usar clases.

SPONSORED Instabug helps you identify and resolve severe crashes quickly. You can retrace in-app events and know exactly which line of code caused the crash along with environment details, network logs, repro steps, and the session profiler. Ask more questions or keep users up-to-date with in-app replies straight from your dashboard. Instabug takes data privacy seriously, so no one sees your data but you! See more detailed features comparison and try Instabug's crash reporting SDK for free.

BUY OUR BOOKS
Buy Pro Swift 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 (Vapor Edition) 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 Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!