Saltar al contenido
K
Swift: el lenguajePaso a paso20 min Básico

Valores por defecto e inout

Al terminar: Dar valores por defecto a los parámetros y modificar un valor con inout.

Al terminar tendrás en Puntos.swift dos funciones más: encabezadoDeJornada, con un parámetro que tiene valor por defecto, y sumarGol, que cambia la variable de quien la llama con inout. Los 5 tests de FuncionesTests las comprueban, y main.swift imprime Fecha 1 de 3 y Goles de Ana: 2.

Importa porque un valor por defecto te ahorra escribir en cada llamada lo que casi siempre es igual, y porque inout es la forma de Swift de decir «esta función cambia mi variable», con un & que se ve desde fuera. Verás las dos ideas en la biblioteca estándar y en las APIs de Apple.

Temas: valores por defecto de parámetros e inout.

Un parámetro con valor por defecto

Un valor por defecto es el valor que tiene un parámetro cuando quien llama no lo da. Se escribe con = después del tipo, y al llamar la función puedes omitir ese argumento. Es como el formulario de una tienda en línea con el país ya rellenado con «España»: si no cambias nada, se queda ese; si quieres otro, lo escribes.

Cómo se declaraCómo se llamaQué pasa
func f(goles: Int = 1)f() o f(goles: 2)Valor por defecto: se puede omitir

inout: un parámetro que sale cambiado

Un parámetro normal es una constante: n += 1 dentro de la función da left side of mutating operator isn't mutable: 'n' is a 'let' constant. Con inout, el valor de la variable de quien llama entra en la función, la función lo cambia y el valor nuevo vuelve a la variable al terminar. Quien llama pone & delante para que se vea. Solo se puede pasar una variable, no un número escrito directamente (como 5) ni un let: con un let el compilador dice cannot pass immutable value as inout argument: 'reloj' is a 'let' constant.

En Kotlin no existe: sus parámetros nunca se pueden cambiar, y para sacar un valor cambiado de una función escribías return con el valor nuevo y quien llamaba lo guardaba (goles = sumarGol(goles)).

Las dos ideas con otros datos, el saludo de una tienda y sus cajas en el almacén:

ejemplo mínimo: valor por defecto e inout (con comentarios)
// `a nombre`: etiqueta a, nombre interno nombre.
// `= "!"`: valor por defecto, así que con: se puede omitir.
func saludar(a nombre: String, con signo: String = "!") -> String {
    "Hola, \(nombre)\(signo)"
}
 
// `inout`: el valor entra, la función lo cambia y el valor nuevo vuelve a la variable.
func reponer(_ existencias: inout Int, unidades: Int = 1) {
    existencias += unidades               // sin inout, esto no compilaría: sería una constante
}
 
print(saludar(a: "Lucía"))                // Hola, Lucía!
print(saludar(a: "Lucía", con: "."))      // Hola, Lucía.
 
var cajas = 10                            // tiene que ser var: la función la va a cambiar
reponer(&cajas)                           // 11: unidades vale 1 si no se dice
reponer(&cajas, unidades: 5)              // 16
print(cajas)                              // 16

Lo que imprime:

Hola, Lucía!
Hola, Lucía.
16

Kotlin frente a Swift

Kotlin (tu código de Kotlin)Swift
encabezadoDeJornada(numero = 1, totalDeJornadas = 3)encabezadoDeJornada(numero: 1, de: 3)
prefijo: String = "Jornada"prefijo: String = "Jornada"
Un parámetro nunca se cambiainout y & al llamar

Reto · Ejercicio: el reloj del partido

En tu Playground, escribe sumarMinutos, que reciba una variable de minutos y le sume, en la propia variable, los minutos que le digas; si no le dices cuántos, suma 5. Y cartel, que reciba un minuto con la etiqueta en y devuelva «Minuto 25» para el 25. Con una variable que empiece en 0, llama a sumarMinutos una vez sin decir cuántos y otra con 20, e imprime su cartel:

Minuto 25

Después intenta llamar a sumarMinutos con una constante en lugar de la variable y lee el mensaje del compilador.

Ver solución
solución del ejercicio
// inout: la variable de quien llama sale cambiada. cuantos vale 5 si no se dice.
func sumarMinutos(_ minuto: inout Int, cuantos: Int = 5) {
    minuto += cuantos
}
 
// Etiqueta en (fuera) y nombre minuto (dentro).
func cartel(en minuto: Int) -> String {
    "Minuto \(minuto)"
}
 
var reloj = 0
sumarMinutos(&reloj)               // 5
sumarMinutos(&reloj, cuantos: 20)  // 25
print(cartel(en: reloj))           // Minuto 25

Con let reloj = 0 y sumarMinutos(&reloj), el compilador dice cannot pass immutable value as inout argument: 'reloj' is a 'let' constant.

Paso · encabezadoDeJornada y sumarGol

Objetivo: ampliar Puntos.swift con las dos funciones nuevas y main.swift con sus llamadas. Al terminar, xcrun swift run torneo-cli imprime Fecha 1 de 3 y Goles de Ana: 2, y xcrun swift test da 16 tests.

Marcador-Swift/Torneo/
Sources/
Torneo/Puntos.swiftse modifica
torneo-cli/main.swiftse modifica
Tests/TorneoTests/FuncionesTests.swiftse crea y se rellena con «Copiar fichero»
Dos ficheros que crecen y uno nuevo.

Abre una Terminal y escribe cd ~/Marcador-Swift/Torneo. Vas en dos tramos, y compilas y ejecutas al terminar cada uno.

Tramo 1 · encabezadoDeJornada: etiquetas y valor por defecto

Intenta escribirlo tú, al final de Puntos.swift: encabezadoDeJornada, una función pública que recibe el número de jornada con la etiqueta numero, el total con la etiqueta de y, opcionalmente, un prefijo (si no se da, «Jornada»), y devuelve un texto como «Jornada 1 de 3».

Intenta primero. Cuando termines, compara con la solución.Mostrar el código
Puntos.swift: tramo 1, encabezadoDeJornada, al final del fichero
// `de total` es etiqueta `de` y nombre interno `total`.
// `prefijo` tiene valor por defecto: se puede omitir al llamar.
public func encabezadoDeJornada(numero: Int, de total: Int, prefijo: String = "Jornada") -> String {
    "\(prefijo) \(numero) de \(total)"
}

En main.swift, sustituye el bucle de jornadas (el for que imprimía Jornada \(jornada) de \(totalDeJornadas)). Intenta escribirlo tú: el mismo bucle, que ahora imprime cada encabezado con encabezadoDeJornada, y después una línea más, la jornada 1 de 3 con el prefijo «Fecha». Es un fragmento: el resto del fichero no cambia. Lo que debes ver tras Torneo: Copa Barrio:

Jornada 1 de 3
Jornada 2 de 3
Jornada 3 de 3
Fecha 1 de 3
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
main.swift: fragmento, el bucle de jornadas
for jornada in 1...totalDeJornadas {
    // Las etiquetas `numero:` y `de:` son obligatorias y van en este orden.
    print(encabezadoDeJornada(numero: jornada, de: totalDeJornadas))
}
// Con el tercer argumento, el prefijo deja de ser "Jornada".
print(encabezadoDeJornada(numero: 1, de: 3, prefijo: "Fecha"))

Ejecuta xcrun swift run torneo-cli: tras Torneo: Copa Barrio debe salir Jornada 1 de 3, Jornada 2 de 3, Jornada 3 de 3 y Fecha 1 de 3, y luego los nueve marcadores de siempre.

Tramo 2 · sumarGol: inout

Pieza nueva: inout en la declaración del parámetro y & delante del argumento al llamar. El patrón completo, comentado, es reponer(_:unidades:) del ejemplo mínimo de arriba: vuelve a él si dudas.

Intenta escribirlo tú, al final de Puntos.swift: sumarGol, una función pública que recibe con la etiqueta a un marcador y le suma un gol, de modo que quien la llama ve su variable cambiada.

Intenta primero. Cuando termines, compara con la solución.Mostrar el código
Puntos.swift: tramo 2, sumarGol, al final del fichero
// `inout`: el valor entra, la función lo cambia y el valor nuevo vuelve a quien llamó.
public func sumarGol(a marcador: inout Int) {
    marcador += 1
}

En main.swift, después del fragmento de los titulares (los siete marcadores de la lección anterior) y antes del comentario // Una lista de tres jugadores, intenta escribir tú unas líneas que guarden los goles de Ana empezando en 0, le sumen dos goles con sumarGol y los impriman. Lo que debes ver:

Goles de Ana: 2
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
main.swift: fragmento, los goles de Ana
var golesDeAna = 0            // tiene que ser `var`: la función la va a cambiar
sumarGol(a: &golesDeAna)      // el `&` obliga a que se vea que puede cambiar
sumarGol(a: &golesDeAna)
print("Goles de Ana: \(golesDeAna)")   // Goles de Ana: 2

Ejecuta de nuevo: tras los siete titulares debe salir Goles de Ana: 2, y después, lo de la lección de opcionales.

El error del & olvidado

Si llamas sumarGol(a: golesDeAna) sin el &, el compilador se detiene con este mensaje, precedido por la ruta y la línea de tu main.swift:

error: passing value of type 'Int' to an inout parameter requires explicit '&'

El & no es decoración: obliga a que quien llama vea que su variable puede cambiar.

Los tests

Los cinco tests de FuncionesTests.swift no los escribes: son comprobaciones de encabezadoDeJornada y de inout, funciones que ya has escrito. Rellénalos con el botón Copiar fichero (copia al portapapeles el fichero ya escrito, como en guard let y textos que no son números):

  1. Crea Tests/TorneoTests/FuncionesTests.swift: clic derecho sobre Tests → TorneoTests → New File from Template… → Swift File → Next, escribe FuncionesTests y pulsa Create. O, en la Terminal, touch Tests/TorneoTests/FuncionesTests.swift.
  2. Pulsa el botón de abajo.
  3. Haz clic dentro del fichero, selecciona todo con Cmd+A y pega con Cmd+V.
FuncionesTests.swift

Uno enseña algo nuevo: prueba inout comprobando que la variable de quien llama cambia.

el test que enseña algo
    @Test func inoutCambiaLaVariableDeQuienLlama() {
        var goles = 0
        sumarGol(a: &goles)
        sumarGol(a: &goles)
        #expect(goles == 2)
    }

Punto de control

  • Desde ~/Marcador-Swift/Torneo, xcrun swift build termina con Build complete!.
  • Desde ~/Marcador-Swift/Torneo, xcrun swift run torneo-cli imprime Fecha 1 de 3, los siete titulares (de 0-0: Sin goles a 1-3: Gana el visitante, con 5-1: Goleada entre ellos) y Goles de Ana: 2, antes del bloque de opcionales.
  • Desde ~/Marcador-Swift/Torneo, xcrun swift test termina con Test run with 16 tests in 3 suites passed (ignora la línea Executed 0 tests de XCTest): los 11 anteriores (4 de PuntosTests y 7 de JugadorTests) y los 5 de FuncionesTests que copiaste.
  • Sabes decir para qué sirve el & al llamar a una función con inout y por qué la variable que le pasas tiene que ser var.

Comprueba lo que entendiste

5 preguntas · necesitas 4 para completar la lección

  1. 1.Tienes func sumarGol(a marcador: inout Int) y var goles = 0. ¿Cuál es la llamada correcta?

  2. 2.Una función se declara func saludar(a nombre: String, con signo: String = "!"). ¿Cuál de estas llamadas es válida?

  3. 3.Tienes func reponer(_ existencias: inout Int) y escribes let cajas = 10 y reponer(&cajas). ¿Qué pasa?

  4. 4.Dentro de func f(_ n: Int), escribes n += 1. ¿Qué pasa?

  5. 5.En Kotlin escribías encabezadoDeJornada(numero = 1, totalDeJornadas = 3). ¿Qué diferencia tiene la llamada en Swift, encabezadoDeJornada(numero: 1, de: 3)?

Faltan 5 de 5