Concepto de función
Al igual que en cualquier otro lenguaje de programación, uno de los puntos básicos que todo desarrollador Swift debe conocer es la manera de trabajar con funciones.
Podríamos decir que forma parte del núcleo de un lenguaje y provee un sinfín de beneficios al momento de escribir código claro y mantenible.
Una función es una porción de código que se encapsula bajo un nombre y puede ser invocado las veces que sea necesario.
La primer ventaja que ofrece es la posibilidad de reutilizar esa funcionalidad evitando tener que escribir el algoritmo repetidas veces a lo largo del programa. Se espera que el nombre asignado a la función sea descriptivo y relacionado a la tarea que la misma realiza.
La forma en la que las funciones están pensadas en Swift le da al desarrollador una flexibilidad considerable al momento de utilizarlas.
En este sentido, es posible escribir funciones súper básicas como así también otras mucho más rebuscadas.
Para poder escribir una función, se utiliza la palabra reservada func
seguida del nombre de la misma. La forma más básica sería:
func saludar() { print("Hola mundo!") } saludar() //Devuelve: //Hola mundo!
Parámetros y valores de retorno
Asimismo, las funciones pueden recibir valores de entrada para poder trabajar en función a ellos, los cuales son conocidos como parámetros. De la misma manera, una función puede devolver un valor a quien lo invoca una vez finalizada su ejecución, conocido como valor de retorno.
//Función con un parámetro func saludar(nombre: String) { print("Hola \(nombre)!") } saludar(nombre: "Gabriel") //Devuelve: //Hola Gabriel! //Función con un parámetro y un valor de retorno func saludarA(nombre: String) -> String { return "Hola \(nombre)!" } let saludo = saludarA(nombre: "Gabriel") print(saludo) //Devuelve: //Hola Gabriel!
Como muestran los ejemplos anteriores, la invocación a una función se realiza escribiendo el nombre de la misma seguido de paréntesis. Si la función posee parámetros, los mismos se incluyen dentro de los paréntesis y si la función devuelve un valor se utiliza la flecha -> seguida del tipo de dato a devolver. Este tipo puede ser cualquiera, ya sea un String
, Int
, una tupla, una clase o struct que definamos nosotros, un opcional, etc. Asimismo, se debe utilizar algún mecanismo para recibir ese dato (por ejemplo, guardarlo en una constante o variable).
Los valores de entrada pasados a una función al momento de invocarla se denominan Argumentos. Estos argumentos deben coincidir con el tipo de dato de los parámetros esperados de la función.
Por otro lado, el conjunto conformado por el nombre de la función, los parámetros y su valor de retorno conforman la definición de la función, y no puede haber en un mismo código dos funciones con la misma definición.
Cuando tenemos dos o más funciones con el mismo nombre pero distinta definición, decimos que esa función está sobrecargada o que existe una sobrecarga de la misma.
En el caso en que se desee usar dos o más parámetros, se deben separar por una coma en la definición de la función:
//Función con dos parámetros func saludar(nombre: String, edad: Int) { print("Felices \(edad) años \(nombre)!") } saludar(nombre: "Gabriel", edad: 29) //Devuelve //Felices 29 años Gabriel!
Devolver más de un dato
La forma que nos brinda Swift para retornar más de un valor es mediante las tuplas. En este caso, al momento de invocar la función podemos hacer referencia a cualquiera de los datos obtenidos simplemente utilizando su nombre o posición en la tupla:
//Función que devuelve más de un dato func minMax(array: [Int]) -> (min: Int, max: Int) { var minimo = array[0] var maximo = array[0] for valor in array[1..<array.count] { if valor < minimo { minimo = valor } else if valor > maximo { maximo = valor } } return (minimo, maximo) } let notas = minMax(array: [1, 2, 10, 8, 5]) print("La nota mas baja es \(notas.min) y la mas alta es \(notas.max)") //Devuelve: //La nota más baja es 1 y la más alta es 10
Etiquetas de argumento
Cuando especificamos nuestros parámetros podemos definir una etiqueta de argumento (argument label), la cual se utiliza al momento de llamar a la función y se especifica delante del nombre del parámetro en la definición de la misma. En este sentido, el parámetro se usa internamente en la implementación de la función cuando se requiere hacer uso de él.
//Etiquetas de argumento func saludar(a nombre: String, porCumplir edad: Int){ print("Felices \(edad) años \(nombre)!") } saludar(a: "Gabriel", porCumplir: 30) //Devuelve: //Felices 30 años Gabriel!
Como se aprecia en el ejemplo, se está usando un argument label por cada parámetro. Internamente en la función se usa el nombre del parámetro, pero al momento de invocarla solo se usan las etiquetas de argumento. Nótese lo claro que resulta entender lo que hace la función saludar(a:porCumplir:)
al momento de invocarla.
En el caso de que no se especifique una etiqueta de argumento, el mismo nombre de parámetro funciona como nombre de parámetro y etiqueta. Sin embargo, si se desea omitir por completo la etiqueta al momento de invocar a la función, se utiliza un guión bajo (_):
//Omitiendo argument label func sumar(_ primerNumero: Int, _ segundoNumero:Int) -> Int { return primerNumero + segundoNumero } print("El resultado de 4 + 5 es \(sumar(4,5))") //Devuelve: //El resultado de 4 + 5 es 9
Parámetros con valores por defecto
Otra posibilidad que nos habilita Swift es la de asignar un valor por defecto a los parámetros, de forma tal que obtengan ese valor en caso de que no se le pase ninguno al momento de invocar a la función.
En el caso de utilizar esta metodología, se recomienda listar primero los parámetros que no tienen valores por defecto (que en la mayoría de los casos son más representativos para la función) y luego los que sí tienen.
//Parámetros por defecto func saludar(a nombre:String, cumpleAños:Bool = false) { if cumpleAños { print("Feliz cumpleaños \(nombre)") } else { print("Buen día \(nombre)") } } saludar(a: "Gabriel", cumpleAños: true) saludar(a: "Gabriel") //Devuelve: //Feliz cumpleaños Gabriel //Buen día Gabriel
Como se aprecia en el ejemplo anterior, al tener un parámetro opcional la función puede ser llamada incluyendo ese argumento o no.
Parámetros variádicos
Este tipo de parámetros permite especificar una cantidad de cero o más valores de un tipo determinado. Se utiliza para dar flexibilidad al momento de usar la función para pasar una cantidad variables de valores sabiendo que la misma siempre va a trabajar correctamente.
Para indicar que un parámetro es variádico, se usan 3 puntos suspensivos luego del tipo del parámetro, e internamente ese valor se usa como un array.
//Parámetros variádicos func sumar(numeros:Int…) -> Int { var suma:Int=0 for valor in numeros { suma = suma+valor } return suma } print(“La suma es igual a \(sumar(numeros: 4,6,2,3,10,6,7,5,4))”) //Devuelve: //La suma es igual a 47
Las funciones pueden tener 1 solo parámetro variádico. En caso de que se necesite usar varios parámetros, siendo uno solo de ellos variádico, éste debe quedar al final.
Parámetros in-out
Los parámetros que usamos en nuestras funciones son constantes, es decir que no pueden ser modificadas internamente en la función. Si por ejemplo quisiéramos agregar un número más al ejemplo anterior, recibiríamos este error:
error: cannot use mutating member on immutable value: ‘numeros’ is a ‘let’ constant numeros.append(3)
Esta característica funciona así de manera intencionada para evitar que se cambie el valor de un parámetro por error, lo que puede causar comportamientos extraños a lo largo del código y genere una alta dificultad para encontrar el motivo.
De todas maneras, si lo que queremos es modificar los valores de los parámetros internamente en la función y que ese cambio persista incluso cuando la función termina, debemos usar el modificador inout
entre el nombre del parámetro y su tipo.
Existen unas reglas a tener en cuenta:
- Al momento de invocar la función, si el parámetro es
inout
, solo pueden pasarse variables. Constantes o literales no están permitidos ya que justamente no pueden ser modificados - Los parámetros variádicos no pueden ser
inout
- Los parámetros
inout
no pueden tener valores por defecto - Se debe utilizar un ampersand (&) delante de la variable en el llamado a la función
//Parámetros inout func cambiarDosEnteros(_ a: inout Int, _ b: inout Int) { let auxiliar = a a = b b = auxiliar } var numero1 = 5 var numero2 = 8 print("Numero1: \(numero1)") print("Numero2: \(numero2)") cambiarDosEnteros(&numero1, &numero2) print("Numero1: \(numero1)") print("Numero2: \(numero2)") //Devuelve //Numero1: 5 //Numero2: 8 //Numero1: 8 //Numero2: 5
El uso del ampersand (&) viene del lenguaje C y se utiliza para indicar la posición en memoria de la variable. Como el objetivo es modificar el valor del parámetro dentro de la función, se pasa su dirección para poder introducir allí el nuevo dato.
Tipo de una función
Todas las funciones tienen su propio tipo, que está formado por el tipo de sus parámetros y el tipo de su valor de retorno. Veamos algunos ejemplos:
func saludar() { print("Hola mundo!") }
El tipo de esta función es () -> Void
, o lo que es lo mismo “una función que no recibe parámetros y devuelve Void”
func saludarA(nombre: String) -> String { return "Hola \(nombre)!" }
El tipo de esta función es (String) -> String
, o lo que es lo mismo “una función que recibe un parámetro del tipo String y devuelve un String”
En este sentido, se pueden definir variables o constantes del tipo de una función. Por ejemplo:
//Tipos de una función func sumarDosNumeros(_ a: Int, _ b: Int) -> Int { return a + b } func multiplicarDosNumeros(_ a: Int, _ b: Int) -> Int { return a * b } var calculo:(Int,Int) -> Int = sumarDosNumeros print("El resultado de sumar 3 + 7 es \(calculo(3,7))") calculo = multiplicarDosNumeros print("El resultado de multiplicar 3 x 7 es \(calculo(3,7))") //Devuelve: //El resultado de sumar 3 + 7 es 10 //El resultado de multiplicar 3 x 7 es 21
Lo que hicimos fue definir dos funciones distintas pero del mismo tipo: ambas reciben dos parámetros Int
y devuelven un Int
. Luego, definimos una variable de ese tipo (Int, Int) -> Int
al cual primero lo igualamos a la primer función y luego a la segunda.
De la misma manera, se puede utilizar a las funciones como parámetros, definiéndolos con el tipo correspondiente. Esto permite que parte de la implementación de la función se la deje a la clase o struct que la invoca. Más adelante veremos cómo funcionan las clases en swift.
func imprimirResultado(_ operacion:(Int,Int) -> Int, _ a:Int, _ b:Int ) { print("El resultado es \(operacion(a,b))") } imprimirResultado(sumarDosNumeros, 10, 60) //Devuelve: //El resultado es 70
En el ejemplo, la función imprimirResultado(_:_:_:)
solo hace eso, imprime un resultado de una operación que no se conoce al momento de la implementación pero que se recibe por parámetro. Recién al momento de llamar a la función se sabe que lo que se quiere es sumar los números pasados a la misma.
Ejemplos completos
//Funcion sin parametros func saludar() { print("Hola mundo!") } saludar() //Devuelve: //Hola mundo! //Función con un parámetro func saludar(nombre: String) { print("Hola \(nombre)!") } saludar(nombre: "Gabriel") //Devuelve: //Hola Gabriel! //Función con un parámetro y un valor de retorno func saludarA(nombre: String) -> String { return "Hola \(nombre)!" } let saludo = saludarA(nombre: "Gabriel") print(saludo) //Función con dos parámetros func saludar(nombre: String, edad: Int) { print("Felices \(edad) años \(nombre)!") } saludar(nombre: "Gabriel", edad: 29) //Devuelve //Felices 29 años Gabriel! //Función que devuelve más de un dato func minMax(array: [Int]) -> (min: Int, max: Int) { var minimo = array[0] var maximo = array[0] for valor in array[1..<array.count] { if valor < minimo { minimo = valor } else if valor > maximo { maximo = valor } } return (minimo, maximo) } let notas = minMax(array: [1, 2, 10, 8, 5]) print("La nota más baja es \(notas.min) y la más alta es \(notas.max)") //Devuelve: //La nota más baja es 1 y la más alta es 10 //Etiquetas de argumento func saludar(a nombre: String, porCumplir edad: Int){ print("Felices \(edad) años \(nombre)!") } saludar(a: "Gabriel", porCumplir: 30) //Devuelve: //Felices 30 años Gabriel! //Omitiendo argument label func sumar(_ primerNumero: Int, _ segundoNumero:Int) -> Int { return primerNumero + segundoNumero } print("El resultado de 4 + 5 es \(sumar(4,5))") //Devuelve: //El resultado de 4 + 5 es 9 //Parámetros por defecto func saludar(a nombre:String, cumpleAños:Bool = false) { if cumpleAños { print("Feliz cumpleaños \(nombre)") } else { print("Buen día \(nombre)") } } saludar(a: "Gabriel", cumpleAños: true) saludar(a: "Gabriel") //Devuelve: //Feliz cumpleaños Gabriel //Buen día Gabriel //Parámetros variádicos func sumar(numeros:Int...) -> Int { var suma:Int=0 for valor in numeros { suma = suma+valor } return suma } print("La suma es igual a \(sumar(numeros: 4,6,2,3,10,6,7,5,4))") //Devuelve: //La suma es igual a 47 //Parámetros inout func cambiarDosEnteros(_ a: inout Int, _ b: inout Int) { let auxiliar = a a = b b = auxiliar } var numero1=5 var numero2=8 print("Numero1: \(numero1)") print("Numero2: \(numero2)") cambiarDosEnteros(&numero1, &numero2) print("Numero1: \(numero1)") print("Numero2: \(numero2)") //Devuelve //Numero1: 5 //Numero2: 8 //Numero1: 8 //Numero2: 5 //Tipos de una función func sumarDosNumeros(_ a: Int, _ b: Int) -> Int { return a + b } func multiplicarDosNumeros(_ a: Int, _ b: Int) -> Int { return a * b } var calculo:(Int,Int) -> Int = sumarDosNumeros print("El resultado de sumar 3 + 7 es \(calculo(3,7))") calculo = multiplicarDosNumeros print("El resultado de multiplicar 3 x 7 es \(calculo(3,7))") //Devuelve: //El resultado de sumar 3 + 7 es 10 //El resultado de multiplicar 3 x 7 es 21 func imprimirResultado(_ operacion:(Int,Int)->Int, _ a:Int, _ b:Int ) { print("El resultado es \(operacion(a,b))") } imprimirResultado(sumarDosNumeros, 10, 60) //Devuelve: //El resultado es 70