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
yvar
- 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.")
}