imagen

Estructuras y clases

Lo siguiente son apuntes generales que viene bien tener a mano cuando estas partiendo, no pretende ser ni un curso ni un tutorial, recuerda usarlo siempre en compañía de un adulto responsable y siempre consultar la documentación oficial.

Los ejemplos son sólo ilustrativos, por lo cual algunos rayan en lo absurdo.

Definiendo estructuras y clases

/* Definimos una estructura */
struct Resolution{
  var width = 100
  var height = 100
}

/* Definios una clase */
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}


/* Instanciamos una estructura */
var MyResolution = Resolution()

/* Instanciamos una clase */
let MyVideoMode = VideoMode()


/* Leemos una de sus propiedades */
print(MyResolution.width)
// output : 100

print(MyVideoMode.resolution.width)
// output : 100

/* Modificando un valor de nuestra estructura */
MyResolution.width = 500

print(MyResolution.width)
// output : 500

/* Instanciamos una estructura con valores iniciales */
var MySecondResolution = Resolution( width: 666, height: 788)

print(MySecondResolution.width)
// output : 666

Copiando estructuras

La copia de estructuras se hace copia por valor, no por referencia, si modificas la copia, solo se modifica la copia no su original.

/* Instanciamos una estructura */
let hd = Resolution(width: 1920, height: 1080)

/* Copiamos a una nueva variable */
var cinema = hd

/* Modificamos la instancia copiada */
cinema.width = 2048

/* Leemos el valor de la segunda instancia (la copia) */
print(cinema.width)
// output : 2048

/* Leemos el valor de la primera instancia (la original) */
print(hd.width)
// output : 1920

/* Pasa lo mismo con los enumerados */
enum CompassPoint {
  case north, south, east, west
}

/* Valor inicial */
let initialDirection = CompassPoint.west

/* Hacemos una copia */
var currentDirection = initialDirection

/* Modificamos la copia */
currentDirection = .south

/* El original de mantiene */
print("The initial direction is \(initialDirection)")
// Prints "The initial direction is west"

/* La copia es modificada */
print("The current direction is \(currentDirection)")
// Prints "The current direction is south"

Copiando clases

La copia de la instancia de una clase se hace copia por referencia, no por valor, si modificas la copia, se modifica la copia y su original.

/* Definimos una clase */
class Camera {
  var name: String?
}

/* Instanciamos la clase */
let Camera1 = Camera()

/* Definimos la propiedad name de Camera1 */
Camera1.name = "Front camera"

/* Hacemos una copia */
let Camera2 = Camera1

/* Modificamos la propiedad name de Camera2 */
Camera2.name = "Back camera"

/* Ambos objetos sufrieron la modificación */
print(Camera1.name!)
print(Camera2.name!)
// 100
// 100

Comparando clases (objetos) en swift

Básicamente, cuando copiamos una clase, se copia la referencia al espacio de memoria, por tanto ambas clase, apuntan al mismo espacio de memoria, si queremos averiguar si dos instancias del mismo objeto apuntan al mismo espacio de memoria, podemos usar los comparadores de identidad.

  • Idénticos ===
  • No idénticos !==
/* Clase 1*/
class Car {
  var name: String?
  var model: String?
}

/* Clase 2 */
class Boat {
  var name: String?
  var model: String?
}


var MyCar =  Car()

var MyBoat =  Boat()

/* Coparamos */
if( MyCar === MyBoat ){
  print("Objetos iguales")
}else{
  print("Objetos diferentes")
}
// Objetos diferentes

/* Hacemos un nuevo objeto de la misma clase */
var MyCar2 = Car()

/* Comparamos */
if( MyCar === MyCar2){
  print("Objetos iguales")
}else{
  print("Objetos diferentes")
}
// Objetos diferentes

/* Copiamos una instancia ya creada (objeto) */
var CopyOfMyBoat = MyBoat

/* Comparamos */
if(MyBoat === CopyOfMyBoat){
  print("Objetos iguales")
}
// Objetos iguales

Propiedades

  • Stored properties (Almacenada): Las normalitas let y var
  • Lazy properties (Almacenada diferida): Su valor inicial no se calcula hasta la primera vez que se usa.
  • Computed properties (calculadas) : Su valor se calcula a partir de otras variables, pueden ser leídas y también definidas.
  • Read-Only Computed Properties (Calculadas de solo lecturas): Igual que las anteriores, pero son de solo lecturas.
  • Property Observers (Observadas) : Propiedades que ejecutan una función, cuando son modificadas.

    • willSet
    • didSet.

Stored properties

Almacenadas, las normalitas let y var.

struct Position  {
  var xPosition : Int
  var yPosition : Int
  let namePosition : String
}

/* Creamos nuestro objeto */
var MyPosition = Position(xPosition: 4, yPosition: 6, namePosition: "Home")

/* Podemos modificar la popiedades de tipo var */
MyPosition.xPosition = 77

lazy properties

Almacenada diferida, su valor inicial no se calcula hasta la primera vez que se usa.

/* Clase que lee un archivo de muchos GB */
class ReadBigFile{
  var filename = "data.csv"
}

/* clase que gestiona varias tareas y hace uso de la clase anterior */
class handlerRequest{
  var data = [String]()
  lazy var dataFromFile = ReadBigFile()
}

/* Se instancia */
let MyWorker = handlerRequest()

/* Trabajamos con la clase */
MyWorker.data.append("Agregamos datos")
MyWorker.data.append("Agregamos mas datos")

/* Hasta aca MyWorker no ha llamado a ReadBigFile, evitando el uso de memoria */

/* Aca leemos la lazy property, y recien se instancia (asigna memoria) a la clase ReadBigFile */
MyWorker.dataFromFile.filename

Computed properties

Calculadas, su valor se calcula a partir de otras variables, pueden ser leídas y también definidas.

/* Definimos estrutura con propiedad calculada*/
struct persona{
  let anioActual = 2020
  var anioNacimiento : Int
  var edad : Int {
    get {
      return anioActual - anioNacimiento
    }
    set(a) {
      anioNacimiento = anioActual - a
    }
  }
  
}

var alguien = persona(anioNacimiento: 1990)
print(alguien.edad)
// 30

alguien.edad = 56
print(alguien.edad)
// 56

Read-Only Computed Properties

Calculadas de solo lecturas, igual que las anteriores, pero son de solo lecturas.

/* Definimos estrutura con propiedad calculada de solo lectura */
struct Viaje{
  var distanciaRecorrida : Int
  var tiempoDelViaje : Int
  var velocidadMedia : Float {
    get {
      return Float(distanciaRecorrida / tiempoDelViaje)
    }
  }
}

/* Instanciamos */
var MiViaje = Viaje(distanciaRecorrida: 450, tiempoDelViaje: 6)

print(MiViaje.velocidadMedia)
// 75.0

Property Observers

Observadas, propiedades que ejecutan una función, cuando son modificadas.

  • willSet: ejecuta antes que el valor sea almacenado.
  • didSet: ejecuta inmediatamente después que el valor es almacenado.
class StepCounter {
  var totalSteps: Int = 0 {
    willSet(newValue) {
      print("newValue \(newValue)")
    }
    didSet(oldValue) {
      print("oldValue \(oldValue)")
    }
  }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// newValue 200
// oldValue 0

stepCounter.totalSteps = 360
// newValue 360
// oldValue 200

Property types

Propiedades que solo existen a nivel de clase ( o estructura ), no pueden ser leídas por instancias, solo por la clase (o estructura).

  • Se declaran usando la palabra static
/* Estructura */
struct SomeStructure {
  var name = "ST1"
  static var storedTypeProperty = "Some value."
  static var computedTypeProperty: Int {
    return 1
  }
}

/* instancia */
var instanceStr = SomeStructure();
print(instanceStr.name)
// ST1

/* Esto no funcionara */
// print(instanceStr.storedTypeProperty)

/* Leyendo valor */
print(SomeStructure.storedTypeProperty)
// Some value.

Cuando queremos declarar una propiedad de clase, pero queremos que una subclase la pueda sobre escribir, debemos usar class

class SomeClass {
  /* Propiedad de clase */
  static var storedTypeProperty = "Some value."
  
  /* Propiedad de clase computada */
  static var computedTypeProperty: Int {
    return 27
  }
  
  /* Propiedad de clase computada que se puede sobre escribir*/
  class var overrideableComputedTypeProperty: Int {
    return 107
  }
}

class child: SomeClass {
  static override var overrideableComputedTypeProperty: Int {
    return 666
  }
}

print(child.storedTypeProperty)
// Some value.

print(child.computedTypeProperty)
// 27

print(child.overrideableComputedTypeProperty)
// 666

Métodos

métodos de instancia

Los métodos de instancia son funciones que pertenecen a instancias de una clase, estructura o enumeración particular.

class Counter {
  var count = 0
  
  func increment(){
    self.count += 1
  }
  
  func increment(by amount:Int){
    self.count += amount
  }
  func reset(){
    self.count = 0
  }
}

// Creamos una instancia
let counter = Counter()

// Llamamos a uno de sus métodos
counter.increment(by: 5)

print(counter.count)
// 5

counter.reset()

print(counter.count)
// 0

métodos de clase

Métodos que pertenecen a las clases, no necesitan instancia para ser llamados.

class someClass{
  static func someMethod(){
    print("Hi!")
  }
}

someClass.someMethod()
// Hi!

Herencia

Una clase puede heredar métodos, propiedades y otras características de otra clase.

  • Cuando una clase hereda de otra, la clase heredada se conoce como una subclase.
  • La clase de la que hereda se conoce como su superclase.
class Vehiculo {
    var velocidad = 0.0
    var descripcion: String {
        return "Viajando a \(velocidad) km/h"
    }
    func bocina() {
        //
    }
}


class Bicicleta : Vehiculo {
  var cesta = false
}

let miBicicleta = Bicicleta()

// Propiedad heredada
print(miBicicleta.descripcion)

// Propiedad de la clase
print(miBicicleta.cesta)
class Tren : Vehiculo {
  var numeroDeVagones = 0
  
  // Sobre escribe el método del padre
  override func bocina() {
    print("choooo chooo")
  }
}

class Auto: Vehiculo {
    var marcha = 1
  // Sobre escribe propiedad calculada
    override var descripcion: String {
        return super.descripcion + " en marcha \(marcha)"
    }
}

let auto = Auto()

auto.velocidad = 25.0
auto.marcha = 3
print("Mi Auto: \(auto.descripcion)")
// Mi Auto: Viajando a 25.0 km/h en marcha 3

class AutomaticCar: Auto {
  // Sobre escribe por una propiedad observada
    override var velocidad: Double {
        didSet {
            marcha = Int(velocidad / 10.0) + 1
        }
    }
}

let automatico = AutomaticCar()
automatico.velocidad = 35.0
print("Automatico: \(automatico.descripcion)")
// Automatico: Viajando a 35.0 km/h en marcha 4

// Evitar que una variable o método sean sobre escritos
class otherClass {
  // esta se podra sobre escribir
  var age = 0
  
  // No se podra sobre escribir
  final var name = "Juan"
}

Constructores

struct Celsius {
  var temperatureInCelsius: Double
  // Al instanciar, se definira el valor de temperatureInCelsius
  init(fromFahrenheit fahrenheit: Double) {
    temperatureInCelsius = (fahrenheit - 32.0) / 1.8
  }
  
  // Al instanciar, se definira el valor de temperatureInCelsius
  init(fromKelvin kelvin: Double) {
    temperatureInCelsius = kelvin - 273.15
  }
}

/* Crea una instancia */
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0

/* Crea otra instancia */
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

Label en los inicializadores

struct Color {
  let red, green, blue: Double
  // Definimos las propiedades con labels
  init(red: Double, green: Double, blue: Double) {
    self.red   = red
    self.green = green
    self.blue  = blue
  }
  init(white: Double) {
    red   = white
    green = white
    blue  = white
  }
}

// Al generar la instancia, debemos indicar las etiquetas
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

let halfGray = Color(white: 0.5)

Constructor con propiedad sin label

class UserAge{
  var age : Int
  // Parametro en el constructor sin label 
  init(_ age: Int){
    self.age = age
  }
}

// Podemos instanciar sin declarar labels
var userAge = UserAge(55)

Constructor con propiedades opcionales

Los valores opcionales no son obligatorios inicializarlo o incluirlos en los constructores

class SurveyQuestion {
  var text: String
  var response: String?
  init(text: String) {
    self.text = text
  }
  func ask() {
    print(text)
  }
}

// Valores predefinidos
class ShoppingListItem {
  var name: String?
  var quantity = 1
  var purchased = false
}
var item = ShoppingListItem()

Constructores en las subclases y sobre escribir propiedades.

/* Clase padre */
class Vehicle {
  var numberOfWheels = 0
  var description: String {
    return "\(numberOfWheels) wheel(s)"
  }
}

/*
 * Inicializador designado 
 * inicializa una propiedad del padre
 */
class Bicycle: Vehicle {
  override init() {
    super.init()
    numberOfWheels = 2
  }
}

/*
 * Inicializador por conveniencia
 * Inicializa propiedades locales ( propias )
 */
class Hoverboard: Vehicle {
  var color: String
  
  // Inicializador por conveniencia (local)
  init(color: String) {
    self.color = color
    // super.init() implicitly called here
  }
  
  // Sobre escribe la variable
  override var description: String {
    return "\(super.description) in a beautiful \(color)"
  }
}

Inicializadores fallidos

A veces es útil definir una clase, estructura o enumeración para la cual la inicialización puede fallar.

/* Creamos nuestra clase y su inicializador */
class Producto{
  let nombre: String
  /* Inicializador opcional (?) que podria retornar nil */
  init?(nombre: String){
    if nombre.isEmpty {
      return nil
    }
    self.nombre = nombre
  }
}

/* Creamos nuestra subclase y su inicializador */
class ItemDelCarrito: Producto{
  let cantidad : Int
  /* Inicializador opcional (?) que podria retornar nil */
  init?(nombre : String, cantidad: Int){
    if cantidad < 1 {
      return nil
    }
    self.cantidad = cantidad
    super.init(nombre: nombre)
  }
}

/* Inicializamos nuestra clase, validando que se inicializa sin retornar nil */
if let calcetines = ItemDelCarrito(nombre: "Calcetines", cantidad: 3){
  print("Inicialización correcta para \(calcetines.cantidad) \(calcetines.nombre) ")
  
  /**
   * Aca ya podemos hacer todo lo que necesitamos con nuestra clasa con la certeza que se inicializo correctamente
   */
}

El desinicializador de una clase, es un set de instrucciones que son ejecutadas inmediatamente antes de que se desasigne (destruya) una instancia de clase.

class cajaFuerte {
  static var balance = 200_000
  
  static func retiro(monto montoSolicitado : Int)->Int {
    let montoARetirar = min(montoSolicitado, balance)
    balance -= montoARetirar
    return montoARetirar
  }
  
  static func deposito(monto montoDepositado: Int){
    balance += montoDepositado
  }
}

class Turno {
   var efectivoEnCaja = 0
  
  init(efectivo: Int){
    self.efectivoEnCaja = cajaFuerte.retiro(monto: efectivo)
  }
  
  func venta(monto ventas : Int){
    self.efectivoEnCaja += ventas
  }
  
  /* Define que ocurrira cuando se destruya la instancia */
  deinit {
    cajaFuerte.deposito(monto: efectivoEnCaja)
  }
}

/* Iniciamos un turno del cajero sacando 50_000 para abrir caja */
var Cajero1 : Turno? = Turno(efectivo: 50_000)

/* Consultamos el balance en cajaFuerte */
print(cajaFuerte.balance)
// 150.000

/* cajero1 hace una venta por 50_000 */
Cajero1!.venta(monto: 20_000)

/* Consultamos efectivo en caja*/
print(Cajero1!.efectivoEnCaja)
// 70000

/* Destruimos el turno */
Cajero1 = nil

/* Consultamos el balance en cajaFuerte */
print(cajaFuerte.balance)
// 220.000 (150.000 + 70.000)

Optional chaining

El encadenamiento opcional es un proceso para consultar y/o llamar a propiedades, métodos y subíndices que puede ser nulo (opcional ?).

class Persona {
  var casa: Casa?
}

class Direccion {
  var calle: String?;
}

class Casa {
  var numeroDeHabitaciones = 1
  var direccion : Direccion?
}

var Peter = Persona();


/*
 * Intentamos leer la propiedad de una clase que no esta definida
 * Al usar optional chainging (?.) obtendremos como resultado nil
 * y no se rompera el código al intentar leer una propiedad de
 * algo que no existe.
 */
var roomPeter = Peter.casa?.numeroDeHabitaciones
print(roomPeter)
// nil


/*
 * La forma correcta de implementarlo, seria algo asi:
 */
if let room = Peter.casa?.numeroDeHabitaciones {
  print("Peter tiene una casa con \(room) habitaciones")
} else {
  print("Peter no tiene casa.")
}


/*
 * Se puede usar en multiples niveles
 */
if let calle = Peter.casa?.direccion?.calle {
  print("Peter vive en la call \(calle)")
} else {
  print("No tenemos la dirección de Peter.")
}