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).
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
{
}
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
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?, 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.
¿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.