Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

5 ideas (más) interesantes de Swift sacadas de otros lenguajes

Swift_Lenguaje

En el post anterior exploramos cinco características de Swift que estaban sacadas de otros lenguajes de desarrollo. En este post exploraremos cinco ideas más que incorpora el nuevo lenguaje de Apple que también pueden encontrarse en otros lenguajes de desarrollo.

1. Sobrecarga de operadores

La sobrecarga de operadores es la posibilidad de utilizar los operadores del lenguaje para nuestros tipos. Así en Swift es posible tener una clase Complex y definir el operador de suma (+) sobre elementos de dicha clase.

Supongamos la siguiente clase Complex:

class Complex {
    var real : Double = 0.0
    var imaginary : Double = 0.0
    init() {}
    init(r:Double, i:Double) {
        self.real = r
        self.imaginary = i
    }
}

Podemos definir el operador de suma para permitir sumar números complejos:

@infix func + (left: Complex, right:Complex)-> Complex {
    var ret = Complex()
    ret.real = left.real + right.real
    ret.imaginary = left.imaginary + right.imaginary
    return ret
}

Es importante señalar que el operador está definido en el ámbito global, es decir no es un método de la clase Complex. Swift no permite que los operadores estén definidos como miembros de una clase, deben estar siempre definidos en el ámbito global.

Una vez tenemos el operador de suma definido ya podemos usar algo parecido a:

var cidentity = Complex(r:0,i:1)
var other = Complex(r:10,i:1)
var add = cidentity + other

Esto permite una sintaxis mucho más natural que si no tuviésemos sobrecarga de operadores (donde entonces, para poder sumar números complejos deberíamos usar algún método llamado Add o similar). A cambio se puede perder en claridad: cuando vemos una expresión a+b tenemos que saber cuáles son los tipos de a y b para deducir exactamente qué hace dicha expresión.

La sobrecarga de operadores se encuentra en otros lenguajes como C++ o C#, cada uno con sus propias peculiaridades. Ahora bien Swift permite crearnos nuestros propios operadores más allá de los que define el propio lenguaje. Para ello primero debemos declarar el operador usando operator:

operator infix +- {}

Con eso reservamos este token (+-) para poder ser usado como operador infijo (es decir "en medio de dos") para cualquier tipo en el que esté definido (recuerda, eso sí, que se definen siempre como funciones globales).

Una vez el operador está reservado podemos definirlo:

@infix func +- (left:Int, right:Int) -> Range<Int> {
    return (left-right)..(left+right)
}

En este caso el operador va en medio de dos números complejos y devuelve un Double. Ahora podemos pues hacer algo como:

var values = 5+-2

Y values será un rango que comprenderá los valores desde el 3 hasta el 7 (ambos inclusive).

2. Genéricos

Genéricos es la posibilidad de definir un parámetro que no toma un valor si no un tipo. Es una característica que se puede encontrar en Java y en C#. Por su parte C++ tiene algo que conceptualmente es parecido (templates) pero es mucho más potente (y complejo) que los genéricos. Swift se alinea más con Java y C#:

class Stack<T> {
    var items = T[]()
    func push(item: T) {
        items.append(item)
    }
    func pop() -> T {
        return items.removeLast()
    }
}

La clase Stack puede contener cualquier tipo de datos, así podemos tener una pila de cadenas:

var stackOfStrings = Stack<String>()

En stackOfStrings el método push acepta un parámetro de tipo String y pop devuelve un objeto String , ya que el parámetro genérico T vale String . Al igual que Java o C# el compilador no nos dejará llamar a ningún método sobre un objeto de tipo T (ya que no sabe lo que es). Para indicarle al compilador que el parámetro T solo puede tomar determinados valores (tipos) podemos usar una (o varias) constraints sobre T:

class Stack<T : XX>

En este caso el parámetro genérico T tan solo puede tomar un valor de un tipo que derive de XX o conforme el protocolo XX (dependiendo de si XX es el nombre de una clase o de un protocolo).

3. Tipado estático... y dinámico

Esa característica era obligatoria en Swift teniendo en cuenta que Swift interacciona con Cocoa y con el runtime de Objective-C, que tiene muchas partes dinámicas. Así pues Swift es un lenguaje con tipado estático (cada variable tiene un tipo definido en tiempo de compilación), pero existe el tipo AnyObject que deshabilita las comprobaciones del compilador:

var myObject : AnyObject = NSDate()
let futureDate = myObject.dateByAddingTimeInterval(10)

La variable myObject está definida como AnyObject y puede contener cualquier objeto. Además podemos llamar propiedades y métodos de dicho objeto (el compilador no verificará que dicho método exista ya que no sabe de que tipo real es el objeto). Eso sí, si el método no existe en ejecución se generará un error. Objective-C tiene, por supuesto, una característica similar (el tipo de datos id) y en C# se puede encontrar también algo parecido (a través de la palabra dynamic).

Un tema a tener en cuenta es que, si queremos realizar llamadas a métodos a través de una variable AnyObject, el objeto asignado debe ser de una clase que conforme el protocolo NSObject. Lo más sencillo es heredar de NSObject. En caso contrario recibiremos un error:

AnyObjectError

El error desaparece si la clase Foo hereda de NSObject.

4. Extensiones

Las extensiones permiten añadir métodos y propiedades a una clase ya existente sin necesidad de modificar el código fuente de dicha clase:

class Foo {
    var value = 0
    func inc() {
        self.value++
    }
}
extension Foo {
    func dec() {
        self.value--
    }
}
var foo = Foo()
foo.inc()
foo.dec()

En este caso la extensión define un método adicional (dec) que luego podemos llamar como si fuese un método propio de la clase. Dentro de los métodos de una extensión podemos usar self para referirnos al objeto extendido (al igual que en los métodos propios de la clase).

Objective-C tiene una característica similar y C# también ofrece algo parecido a través de los métodos de extensión, aunque en C# no pueden añadirse propiedades (sólo métodos). A cambio en C# pueden añadirse métodos de extensión a una interfaz mientras que Swift no permite extender un protocolo.

5. Paso por referencia

En Swift los objetos se pasan por referencia (a no ser que sean structs que entonces se pasa una copia). Así el siguiente código imprime 20:

class Wrapper {
    var value = 0
}
func updateWrapper (wrapperToUpdate: Wrapper) {
    wrapperToUpdate.value = wrapperToUpdate.value + 10
}
var intValue = Wrapper()
intValue.value = 10
updateWrapper(intValue)
println(intValue.value)

La razón es que el objeto Wrapper contenido en la variable intValue se pasa por referencia a la función updateWrapper, por lo que el parámetro wrapperToUpdate apunta al mismo objeto. Por eso al incrementar en 10 el valor de value dentro de la función updateWrapper, se incrementa el valor de la propiedad value en intValue (puesto que ambas referencias apuntan al mismo objeto). Cualquier lenguaje con soporte para clases soporta el paso por referencia ya que es mucho más eficiente que el paso por valor (copiar el objeto). En C# y Java los objetos siempre se pasan por referencia. En C++ depende de si usamos punteros (o referencias) o no.

Pero... ¿qué ocurre si dentro de la función updateWrapper modificamos el valor de dicho parámetro (es decir, asignamos el parámetro a un objeto nuevo)?

func updateWrapper (wrapperToUpdate: Wrapper) {
    wrapperToUpdate = Wrapper()
    wrapperToUpdate.value = 1000
}

Pues que Swift nos da un error:

WrapperError

Eso es debido a que la referencia se pasa por valor (es decir se copia la referencia, no su contenido que quede claro). Por norma general asignar una referencia recibida como parámetro a un nuevo objeto no suele ser necesario, pero en según que casos puede ser interesante. Para esos casos Swift soporta pasar una referencia por referencia utilizando la palabra clave inout:

func updateWrapper (inout wrapperToUpdate: Wrapper) {
    wrapperToUpdate = Wrapper()
    wrapperToUpdate.value = 1000
}

Eso sí, cuando declaramos un parámetro como inout, debemos usar el operador de referenciación (&) al pasar dicho parámetro. Así el siguiente código imprimirá 1000, ya que la referencia intValue es pasada a su vez por referencia y por lo tanto toma el valor del objeto creado dentro de updateWrapper:

var intValue = Wrapper()
intValue.value = 10
updateWrapper(&intValue)
println(intValue.value)

Lenguajes como Objective-C y C++ proporcionan un mecanismo similar (a través de los punteros a puntero) al igual que C# (usando los parámetros ref o out). Java no proporciona ningún mecanismo para pasar una referencia por referencia.

Y aquí terminamos este post con cinco características adicionales de Swift que pueden encontrarse en otros lenguajes. Por supuesto... ¡todavía nos quedan algunas más para explorar, pero lo dejaremos para posts sucesivos! :)

Eduard Tomás Eduard es ingeniero informático, atesora muchos años de experiencia como desarrollador y ha sido galardonado como MVP por Microsoft en diez ocasiones. Colabora con la comunidad impartiendo charlas en formato webcast y en eventos de distintos grupos de usuarios. Es Certified Kubernetes Application Developer. Ver todos los posts de Eduard Tomás

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Comentarios (2) -

Juan Antonio
Spain Juan Antonio

Ante todo agradecerte el articulo que es muy bueno, mi pregunta es como puedo dar formato a un numero que voy a meter en un textfield para que me muestre solo dos decimales, gracias.....

Responder

Eduard Tomàs
Spain Eduard Tomàs

Entiendo que te refieres a hacerlo con Swift. Pues la respuesta es usando las mismas APIs que en Objective-C:
Prueba el siguiente código:

func toDecString(n: Float) -> String {
    var f = NSNumberFormatter()
    f.maximumFractionDigits = 2
    var result = f.stringFromNumber(n)
    return result;
}

Simplemente uso la clase NSNumberFormatter. El código equivalente en Objective-C podría ser:

- (NSString *)toDecString:(NSDecimalNumber *)n
{
    NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
    [f setMaximumFractionDigits:2];
    NSString *result =[NSString stringWithString:[f stringFromNumber:decimalNumber]];
    return result;
}

Como puedes ver el código en Swift es más claro y conciso que en Objective-C... pero la API usada (la clase NSNumberFormatter) es la misma.

Saludos!

Responder

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.