domingo, 20 de septiembre de 2015

Swift: Variables, Opcionales (?), Opcionales Implícitamente Desempaquetados (!) y conversiones entre ellas (as, as? y as!)


!?

Actualmente me encuentro aprendiendo Swift para programar dispositivos iOS,  es un lenguaje con características muy atractivas que me está agradando mucho.  Me gusta sobretodo la seguridad que ofrece al ser muy restricción en las opciones que ofrece para programar algunos aspectos. Cuanto mas restrictivo sea el lenguaje mas seguro será. Algunos programadores sienten que un lenguaje restrictivo cohíbe su libertad al momento de programar, yo creo que confunde el concepto de libertad, libertad es poder jugar con tu hijo por la tardes, en lugar de estar en la madrugada con una sobredosis de café (o de Red Bull), preguntándote porque está mal referenciado un puntero.

Pero antes de nada, ¿Cómo pruebo Swift?


- Es muy fácil, solo necesitas ejecutar XCode en tu Mac y …

- No tengo una Mac

Vale, entonces está más complicado,  se supone que la versión 2.0 de Swift, va a ser libre, lo cual podría implicar ver el lenguaje en otras plataformas como Windows o Linux, pero hasta entonces necesitaríamos una Mac. Existen algunas alternativas, como por ejemplo este compilador en línea para probar pequeños script de Swift e ir acostumbrándonos al lenguaje:


o este


¿Listo? Sigamos entonces…


Unas de las cosas que más me costado comprender son las variables opcionales, sobre todo por su aparente parecido con los tipos de datos Nulleables de C#, por que manejan conceptos parecidos al igual que su sintaxis. Hasta que no comprendí que son cosas completamente diferentes, no entendí el uso de los opcionales. (Así que aunque suene igual, ni los compares, no se parecen en nada).

Variables en Swift


Una característica de las variables en Swift, es que siempre deben tener valor, o más bien siempre deben estar instanciadas, no pueden tener un valor nulo, o nil, como se les llama en Swift.

Esto quiere decir de que cualquier variable que tengamos definida, sea cual sea, siempre tendremos acceso a sus métodos y propiedades, si necesidad de realizar una validación previa.

Por ejemplo si tuviéramos un String declarado, la cadena siempre debiera tener un valor y nunca podría valer nulo, si no inicializamos la cadena el programa simplemente no compilara. En el caso de definiciones de clases, la variables propiedades de esta deben ser obligatoriamente instanciadas en al momento de su declaración o en el constructor del objeto.

El siguiente código es válido, y el método endIndex, siempre estar disponible por que strValor siempre tendrá un “valor”.

var strValor = "Hola"
println(strValor.endIndex)

También el siguiente código es válido:

var strValor:String 
strValor = "Hola" 
println(strValor.endIndex)

El siguiente código directamente no compilara por querer usar la variable antes asignarla un valor (nótese que no dará un error en tiempo de ejecución, sino que simplemente no compilara).

var strValor:String 
println(strValor.endIndex)

El resultado de la compilación será:
 
error: variable 'strValor' used before being initialized

Tampoco es posible asignarle nil (nulo) a la variable, igualmente no compilara. El siguiente código no será valido:

var strValor:String 
strValor = nil 
println(strValor.endIndex)

Siendo el error el siguiente:

error: cannot assign a value of type 'nil' to a value of type 'String' strValor=nil

Variables Opcionales en Swift


Ahora bien y ¿si realmente necesitamos que algunas variables valgan nulo?, es posible que la ausencia de valor tenga un significativo para nosotros, o que directamente no podemos asignar un valor iniciar a nuestras variables. Para lo cual existen los opcionales.

Los opcionales son variables que pueden tener un valor de un determinado tipo o valer nulo. Para crear un opcional de un determinado tipo simplemente agregamos el símbolo ? al tipo, por ejemplo para tener un opcional de String, lo declaramos como String?:

var strValor1:String? 
strValor1="Hola" 
strValor2=nil

Las dos asignaciones anteriores son validas.

Podemos comprobar si el valor asignado es nulo, simplemente preguntándolo con un if, igual que lo haríamos en otros lenguajes:

if strValor1==nil 
    println ("Es NULO") 
}

Sin embargo si queremos usar el valor del opcional debemos realizar un proceso llamado desempaquetamiento, esto consiste en que obtengamos el tipo inherente al opcional, por ejemplo de un opcional de String, el tipo inherente seria el mismo String.

Para desempaquetar un opcional, podemos usar el operador ! o el operador ?, a la derecha de la variable. Cada operador tiene una funcionalidad diferente.

El operador ? indica al compilador que intente desempaquetar la variable y de no poder no continúe la operación con la variable opcional, asignando nil al resultado requerido (si se requiere).  Por ejemplo:

var strValor1:String?
strValor1 = nil
println (strValor1?.endIndex)

Por ejemplo se mostrara en la pantalla nulo (nil), como resultado, pero no fallara

Otra curiosidad del uso del operador ?, es que siempre devuelve un opcional, del tipo esperado, por ejemplo, el tipo esperado para el método endIndex, es un Integer así que devolvería un Integer?, esto es porque el compilador requiere saber siempre con el tipo que se está trabajando (Swift es de tipeado fuerte, con lo que la única manera de estar seguro del tipo al momento de trabajar es trabajar siempre con opcionales Cuando se usa el operador ? , por ejemplo la ejecución del siguiente código:

var strValor1:String?
strValor1 = "hola"
println ("la variable mide  \(strValor1?.endIndex)'")


Dara el siguiente resultado:

la variable mide 'Optional(4)'


El operador ! permite desempaquetar el valor de opcional,  el valor debe existe y en caso de no existir provocara un error, por lo tanto solo debemos ejecutarlo solo en el caso que estemos seguro que el opcional tenga un valor:

var strValor1:String?
strValor1=”Hola”
println(strValor1!.endIndex)

El siguiente código por ejemplo dará un error en tiempo de ejecución al no tener valor el opcional (es nulo puesto que nunca se inicializo)

var strValor1:String?
println(strValor1!.endIndex)

Esto nos supone un problema, puesto que al usar opcional, debemos comprobar siempre antes de usarlos que tengan valores y desempaquetar el valor en cada uso, por ejemplo en el caso del string debiera ser así:

var strValor1:String?
strValor1 = "Hola"
if (strValor1 != nil)
{
    println("Letras \(strValor1!.endIndex)")
    println("Primera letra \(Array(strValor1!)[0])")
    println("Mayusculas \(strValor1!.uppercaseString)")
    println("Minusculas posicion \(strValor1!.uppercaseString)")
}
else 
{
    //Lo que sea
}

El resultado es el siguiente:

Letras 4
Primera letra H
Mayusculas HOLA
Minusculas posicion HOLA

Como vemos cada vez que queremos usar la variable tenemos que desempaquetarla con el operador ! , Swift nos propone la estructura if let, para prevenir realizar este desempaquetado en cada ocasión y que nuestro código quede más comprensible:

var strValor1:String?
strValor1 = "Hola"

if let strValorDesenpacado=strValor1
{
    println("Letras \(strValorDesenpacado.endIndex)")
    println("Primera letra \(Array(strValorDesenpacado)[0])")
    println("Mayusculas \(strValorDesenpacado.uppercaseString)")
    println("Minusculas posicion \(strValorDesenpacado.uppercaseString)")
}else {

    //Lo que sea

}


Aunque pudiera parecer lo contrario la expresión if let, es una expresión completa y no dos por separado (no es un if mas un let). Al detectar el compilar la orden if let, lo que él entiende es “Desempaqueta la variable de la derecha, si puedes hacerlo (es diferente de nil), asígnaselo a la variable de la izquierda y ejecuta el código del if.”


Opcionales Implícitamente Desempaquetados


Los opcionales implícitamente desempaquetados (Implicitly Unwrapped Optionals, a partir de ahora por sencillez solo opcionales implícitos), son una vuelta de tuerca más al tema de los opcionales, son los más parecidos a los variables por referencia de otros lenguajes como .NET o Java, las variables pueden ser nulas o tener un valor, pero no necesitan ser desempaquetadas en ningún caso, se usan sin los operadores ¿ o ! ( de allí la parte implícita), pero si intentamos usar una variable con un valor nulo, esta generada un error:

var srtValor:String!
srtValor="Hola Mundo"
println(srtValor)

//Notese que no es necesario desempacar la varible
println (srtValor.endIndex)
srtValor=nil
//La siguiente instruccion fallara pero se compilara correctamente
println (srtValor.endIndex)

El uso de los Opcional Explícitos entre, otros motivos, es la interoperabilidad con ObjetiveC, donde es más natural trabajar con variables que se comportan de esta forma (pueden tener valor  o ser nulas). Cuando tengamos que interactuar con la API de ObjetiveC (lo cual pasara mucho), sobre todo para recibir valores de esta, casi siempre serán un opcional implícito.

Conversiones de tipo mediante as, as? y as!


El conjunto de operadores as, nos ayudan a convertir objetos de un tipo en objetos de otro, a través de mecamismo de empaquetado/desempaquetado o de hererencia. Para nuestros ejemplos, vamos a contar con tres clases ClaseA, ClaseB , siendo que ClaseB hereda de ClaseA:

class ClaseA
{
    let Mensaje="Hola Mundo"
}

class ClaseB : ClaseA
{

}

Operador as


Usaremos el operador as, cuando el compilador pueda garantizar que la conversión (o cast) es posible, en tiempo de compilación, y que esta siempre es posible, aunque agreguemos mas código en un futuro (no basta con que nosotros como codificaciones estemos seguros, el compilador debe estar seguro al 100% ). Por ejemplo,  es posible convertir una variable de tipo ClaseB en una de ClaseA o en opcional de ClaseA, mediante el operador as, por que el compilador sabe que dicha conversión es posible siempre:

var claValor: ClaseB = ClaseB()

//Conversiones
var clbValor1 =  claValor as ClaseA
println ("El tipo es '\(clbValor1.dynamicType)'")

//Conversiones
var clbValor2 =  claValor as ClaseA?
println ("El tipo es '\(clbValor2.dynamicType)'")

La salida sera:

El tipo es 'main.ClaseB'
El tipo es 'Swift.Optional'

Pero sin embargo no sera posible usar el operador as para convertir una variable de tipo ClaseA en una de tipo ClaseB, por que el compilador no puede garantizar que siempre sea posible, puesto que no todas los clases que hereden de ClaseA son forzosamente de tipo ClaseB, esto se aplica aunque en el código se vea claramente (por una persona)  que la variable si es convertible.

En el siguiente ejemplo fuerzo la conversion de una variable de tipo ClaseA en una de ClaseB (downcasting):

var claValor1: ClaseA = ClaseB()
//Conversiones
var clbValor1 =  claValor1 as! ClaseB
println ("El tipo es '\(clbValor1.dynamicType)'")

El los siguientes ejemplos, convierto la variable opcional de tipo ClaseA, en una de tipo ClaseB (no opcional) y una Opcional de ClaseB:

var claValor2: ClaseA? = ClaseB()
//Conversiones
var clbValor2 =  claValor2 as! ClaseB
println ("El tipo es '\(clbValor2.dynamicType)'")
var clbValor3 =  claValor2 as! ClaseB?
println ("El tipo es '\(clbValor3.dynamicType)'")

Ahora bien que pasaría en el ejemplo anterior, si la variable opcional del ClaseA valiera nulo y quisiera hacer el casting igualmente a una de ClaseB no opcional:

var claValor2: ClaseA? = nil
//Conversiones
var clbValor2 =  claValor2 as! ClaseB
println ("El tipo es '\(clbValor2.dynamicType)'")

var claValor: ClaseA = ClaseB()

//Conversiones
var clbValor =  claValor as ClaseB

El error marcado sera el siguiente:

error: 'ClaseA' is not convertible to 'ClaseB'; did you mean to use 'as!' to force downcast?

¿Por qué si visualmente se ve que se puede hacer un cast, el compilador no nos deja?, bueno, esto es porque en un futuro podríamos hacer asignar otro valor a la variable claValor, e impedir el cast, Swift nos protege de estos posible futuros errores, el siguiente código seria incorrecto y tampoco compilara (incluso visualmente):

var claValor: ClaseA = ClaseB()
claValor = ClaseA()

//Conversiones
var clbValor =  claValor as ClaseB

Operador as!


El operador as!, nos permite forzar el cast, cuando estemos seguros que el posible realizarlo (aunque el compilador no lo este), es una manera de decir al compilador, que nosotros asumimos la responsabilidad sobre posibles fallos del cast. Los siguientes cast son posibles:

var claValor2: ClaseA? = ClaseB()
//Conversiones
var clbValor2 =  claValor2 as! ClaseB
println ("El tipo es '\(clbValor2.dynamicType)'")
var clbValor3 =  claValor2 as! ClaseB?
println ("El tipo es '\(clbValor3.dynamicType)'")

Ahora bien si no es posible el cast, la conversión fallara y fallara en tiempo de ejecución (no en tiempo de compilación), por ejemplo en el siguiente código:

var claValor2: ClaseA = ClaseA()
//Conversiones
var clbValor2 =  claValor2 as! ClaseB

El operador as?


El operador as?, nos devuelve siempre un Opcional del tipo destino de la conversion, por ejemplo si el destino es un string, el resultado siempre sera un string?.

Se usa este operador para realizar cast de los cuales no estamos seguros que sean posible realizarlos, pero no queremos que falle en tiempo de ejecución. Si es posible realizar el cast se nos devolvera un opcional del tipo destino, si no es posible el resultado sera un nulo, por ejemplo:

//Conversiones
var clbValor2 =  claValor2 as? ClaseB
println ("El tipo es '\(clbValor2.dynamicType)'")

Devolvera un opcional de ClaseB (ClaseB?)

Debemos comprobar antes de usarlo si es valor devuelto es nulo, y si no es nulo desempacarlo para obtener, ahora si, un elemento de tipo ClaseB:

var claValor2: ClaseA = ClaseB()

//Conversiones
var clbValor2 =  claValor2 as? ClaseB

if clbValor2 != nil
{
    let cblValor3 = clbValor2!
    println ("El mensaje es '\(cblValor3.Mensaje)'")

}else {

    println ("El objeto no se puede convertir")

}

Es posible simplificar este conjunto de llamadas usando la instrucción if let revisada anteriormente:

var claValor2: ClaseA = ClaseB()

if let clbValor2 = claValor2 as? ClaseB
{
    println ("El mensaje es '\(cblValor3.Mensaje)'")

}else {

    println ("El objeto no se puede convertir")

}

La seccion del if solo se ejecuara si es posible la conversion, y se trabajara dentro del if con el objeto desempaquetado, sin necesidad de realizar ningún otro proceso adicional, la parte del else se lenzara cuando la conversión no haya sido posible.

Y esto es todo


¿Qué futuro tiene Swift?, es dificil saberlo y posiblemente vendra marcado con la salida de Swift 2.0 y si es capas de ofrecer una mayor integracion con XCode (en el cual hace cosas extrañas a veces), igualmente seria interesante ver Swift funcionando en otras plataformas no Mac. Quedamos en espera de observar que pasa en los siguientes meses.

No hay comentarios:

Publicar un comentario