VISITAS:

lunes, 24 de agosto de 2015

SWIFT (V) : Control de flujo

Sea cual sea el lenguaje de programación en el que estemos trabajando, el desarrollo de aplicaciones es siempre un ejercicio de lógica: tomar decisiones en función de ciertas condiciones y ejecutar múltiples veces cierta lógica sobre unos datos. Todo esto se suele denominar control de flujo, y swift tiene su propia sintaxis, no muy diferente por cierto a la de otros lenguajes.

Sentencia for

Swift ofrece dos tipos de for: condition-increment y for-in

Condition-increment for

La sintaxis es similar (aunque no idéntica) a otros lenguajes C-like:

    for var i = 0; i < 15; i++ {
        println(i)
    }

For-in for

Esta estructura de control permite iterar por los datos de una colección (las colecciones se explican más adelante) o por un rango numérico.
La sintaxis es la siguiente:

    for nombre in colección_o_rango {
        ....
    }

Por ejemplo:

    for i in 1...5 {
        println(i)
    }

Este tipo de for también sirve para iterar por los caracteres de un string:

    for c in "Esto es un ejemplo" {
    }

La variable no es obligatorio declararla. Esto puede ser útil cuando, por ejemplo, sólo se quieren contar los elementos de la colección. En este caso se pone el carácter _:

    for  _  in "Esto es una cadena" {
        counter++
    }

Sentencia while

El bucle for se utiliza cuando sabemos de antemano el número de repeticiones. Pero en otras ocasiones necesitamos repetir hasta que se cumpla una determinada condición y no sabemos de antemano el número de repeticiones que habrá que hacer.

Sintaxis de while:

    while condición {
    }

La condición tiene que se de tipo Bool, y se ejecuta hasta que sea false.

Sentencia do .. while

Este bucle es similar al bucle while, excepto que siempre se ejecuta al menos una vez.

    do {
    } while condición

Salir de un bucle: break

A veces es necesario salir del bucle cuando en su interior se detecta alguna condición que obliga a salir. Para ello se utiliza la sentencia break:

    while a > 10 {
        ....
        if otra_condicion {
            break
        }
        ....
    }

Sentencia continue

La sentencia continue hace que volvamos al principio del bucle, saltando todo lo que queda. Suele ser útil para no hacer nada cuando se cumple una determinada condición.

    while a > 10 {
        if personas[a] == "001" {
            continue
        }
        .....
    }

Sentencia if

La sentencia if (y todas sus variantes) es muy similar a la de otros lenguajes de programación:

    if condición_1 {
        .....
    } else if condición_2 {
        .....
    } else if condición_3 {
        .....
    } else {
        .....
    }

Sentencia switch

La sentencia switch es equivalente a if/else if/else, pero se utiliza cuando el número de casos a comprobar es grande:

    switch expresión {
        case valor1:
            .....  // no se pone break
        case valor2:
            .....  // no se pone break
        default:
            ....
    }

Se pueden combinar varios valores:

    switch expresión {
        case valor1, valor2, valor3:
            ..... 
        case valor4, valor5:
            ..... 
        default:
            ....
    }

También se pueden poner rangos:

    switch expresión {
        case valor1 ... valor2:
            ..... 
        default:
            ....
    }

Algo muy novedoso de la sentencia switch en swift es el uso de la cláusula where, que añade una condición adicional a evaluar:

    switch expresión {
        case valor1 where condición:
            ..... 
        default:
            ....
    }




SWIFT (VIII) : Arrays y Diccionarios

Los arrays y los diccionarios de swift son colecciones de otros objetos y son muy similares a los que ya existen en otros lenguajes de programación. La diferencia entre ambos es el modo de acceso a los objetos que contienen: en los arrays se suele acceder por índice y en los diccionarios se suele acceder por clave. Los diccionarios en otros lenguajes de programación se denominan mapas.

Colecciones mutables e inmutables

Antes de empezar a ver tanto arrays como diccionarios, es preciso indicar que estas colecciones en swift pueden ser mutables o inmutables. Una colección inmutable no se puede modificar una vez que se ha inicializado. Para hacer una colección inmutable, la asignamos a una constante (let constante = colección). Por otro lado, las colecciones mutables sí se pueden modificar después de haberse inicializado. Para hacer una colección mutable, se asigna a una variable (var variable = colección).

Arrays

Un array es una colección que guarda múltiples objetos de forma ordenada. Todos los valores que se guardan en un array tienen que ser del mismo tipo.

Inicialización de arrays

Un array puede inicializarse en la creación mediante una colección de valores (array literal):

    var array : [type] = [ value1, value2, value3 ]

Por ejemplo:

    var cadenas : [String] = [ "Hola", "Mundo" ]

Igualmente, podemos crear un array inmutable:

    let plantas : [String] = [ "helecho", "tomillo" ]

No es obligatorio declarar el tipo del array, se puede dejar que el compilador lo infiera (tanto en constantes como en variables):

    let plantas = [ "helecho", "tomillo" ]

También podemos crear un array vacío:

    var lista = [String]()

Existe una forma de crear un array con una serie de elementos repetidos:

    var nombres = [String](count: 12, repeatedValue: "nombre")

Por último, se puede crear un array con la concatenación de otros arrays:

    var lista = lista1 + lista2

Trabajando con arrays

Tamaño de un array:

    array.count

Comprobar si un array está vacío:

    array,isEmpty

Acceder (en lectura y en escritura) a los items de un array (el primer elemento es el índice 0):

    var a = array[3]
    array[3] = 4

Añadir items a un array

Se pueden añadir elementos a un array mediante la función append o mediante el operador +. Los nuevos items se añaden al final del array, aumentando su tamaño:

    array.append("Hola")
    array += [ "Pepe", "Mundo" ]

También se pueden insertar elementos en una posición concreta de un array, desplazando los demás elementos hacia la derecha:

    array.insert("Manzano", atIndex: 3)

Eliminar items de un array

Se puede eliminar un elemento de una posición concreta de un array, desplazando los demás elementos hacia la izquierda:

    array.removeAtIndex(2)

También se puede eliminar el último elemento de un array:

    array.removeLast()

Iterar por un array

Se puede iterar por un array mediante for:

    for arbol in listaArboles {
        println(arbol)
    }

Diccionarios

Los diccionarios en swift, al igual que en otros lenguajes de programación, permiten almacenar datos en forma de parejas clave-valor. Las claves son únicas y permiten acceder a los valores correspondientes. Los diccionarios son colecciones no ordenadas.
Actualmente, swift sólo permite que las claves sean de tipo String, Int, Double y Bool (supongo que más adelante se ampliarán estos tipos utilizando técnicas similares a las de Java).

Inicialización de diccionarios

Forma de crear e inicializar un diccionario:

    var diccionario : [tipo-key: tipo-valor] = [ key1: value1, key2 : value2 ]

Por ejemplo:

    var personas = [ "001":"José Luis", "005":"Pedro" ]

En este caso se podría haber especificado el tipo, pero se puede dejar que el compilador lo infiera:

    var personas : [String: String] = [ "001":"José Luis", "005":"Pedro" ]

Creación de un diccionario vacío:

    var personas = [String: String]()

Trabajando con diccionarios

Número de elementos en un diccionario:

    personas.count

Acceso a un elemento:

    personas["001"]

Escritura:

    personas["001"] = "Samuel"

Añadir elementos a un diccionario

Se puede añadir un elemento a un diccionario directamente, por su clave:

    personas["045"] = "John"

Eliminar elementos de un diccionario

Para eliminar un elemento de un diccionario, ponemos su valor a nil:

    personas["001"] = nil

Iterar por un diccionario

Al igual que ocurría con los arrays, se puede iterar por todos los elementos de un diccionario utilizando un bucle for:

    for (id, nombre) in personas {
        println("Id = \(id) es \(nombre)")
    }






jueves, 20 de agosto de 2015

SWIFT (IV) Operadores y expresiones

Swift tiene prácticamente los mismos operadores que casi todos los lenguajes C-like (C, C++, Java, Objective-C, Javascript, etc), así que este artículo no será muy novedoso para los programadores experimentados en alguno de estos lenguajes.

Operador de asignación

El operador de asignación simplemente asigna el resultado de una expresión (a la derecha) a una variable (a la izquierda):

    var x : Int?
    x = 10 + 45
    x = x! + 1

Operadores aritméticos

    -  si es unario cambiar el signo, si es binario resta
    * multiplicación
    / división
    + suma
    % módulo o resto

Operador de asignación compuesta

    x += 3

Es equivalente a:

    x = x + 3

Operadores de incremento y decremento

    x++;

Es equivalente a:

    x = x + 1

Y también:

    x--

Es equivalente a:

    x = x - 1

Operadores de comparación

Igualdad:    ==
Desigualdad:   !=
Comparaciones numéricas:   <, >, <=, >=

Operadores lógicos booleanos

NOT ! invierte el valor de un booleano
OR || retorna true si uno de sus operandos es true
AND && retorna true si sus dos operandos son true
XOR ^ retorna true si sus dos operandos son distintos (true, false o false,true)

Operadores de rango

Los operadores de rango se suelen emplear en bucles.
La sintaxis del operador de rango básico es:

    a ... b

Esto representa el rango de valores entre a y b, ambos incluidos. Por ejemplo, 3 ... 6 representa los números 3, 4, 5 y 6.
Si se desea no incluir el último número:

    a ..< b

En este caso, el número b no va incluido en el rango.

Operador ternario

Al igual que en muchos lenguajes C-like en swift existe también el operador ternario:

    condición ? expresión_true : expresión_false

El resultado de esta expresión depende del valor de la condición. Si la condición es true, entonces el resultado de la expresión será la expresión_true. Si la condición es false, entonces el resultado de la expresión será la expresión_false.

Operadores de bit

Operador unario NOT de bit, cambia los bits a 1 por 0 y los bits 0 por 1:

    var x : UInt8 = 3      // 00000011
    var y : UInt8 = ~x    // 11111100       

Operador binario AND de bit, realiza un AND a nivel de bit de los dos operandos:

    var x : UInt8 = 12        // 00001100
    var y : UInt8 = 5          // 00000101
    var z : UInt8 = x & y   // 00000100

Operador binario OR de bit, realiza un OR a nivel de bit de los dos operandos:

    var x : UInt8 = 12        // 00001100
    var y : UInt8 = 5          // 00000101
    var z : UInt8 = x | y     // 00001101

Operador binario XOR de bit, realiza un XOR a nivel de bit de los dos operandos:

    var x : UInt8 = 12        // 00001100
    var y : UInt8 = 5          // 00000101
    var z : UInt8 = x ^ y    // 00001001

Desplazamiento de bits a la izquierda, metiendo ceros por la derecha:

    var x : UInt8 = 12       // 00001100
    var z : UInt8 = x << 1 // 00011000

Desplazamiento de bits a la derecha, metiendo ceros por la izquierda:

    var x : UInt8 = 12       // 00001100
    var z : UInt8 = x >> 1 // 00000110

    

martes, 18 de agosto de 2015

SWIFT (VII) : Clases y Objetos

Swift es un lenguaje orientado a objetos para desarrollar aplicaciones iOS y OSX.
No es el objetivo de este capítulo explicar los conceptos de orientación a objetos (para eso existen multitud de libros y artículos), sino explicar cómo estos conceptos se implementan con swift.

Clases

Una clase se define de la siguiente forma:

class NombreDeLaClase : ClasePadre {
    // propiedades
    // métodos de instancia
    // metodos de clase
}

Las propiedades son variables y constantes. Se declaran de la misma forma que las variables y constantes en swift, es decir, con var y let.
Los métodos de instancia son funciones que se invocarán a través de un objeto de la clase. Se declaran de forma similar a las funciones de swift, es decir, con func.
Los métodos de clase son comunes a toda la clase y no se necesita un objeto para invocarlos (son similares a los métodos estáticos de otros lenguajes). Se declaran como funciones normales, pero con class func.

Veamos un ejemplo:

class GraphicObject {
    var x : Float = 0
    var y : Float = 0
    func show() {
        println("x=\(x), y=\(y)")
    }
    class func getMaxWidth() -> Float {
        return 240.0
    }
}

Clase derivada:

class Rectangle : GraphicObject {
    var width : Float = 0
    var height : Float = 0
}

Objetos

Declaración de un objeto:

var obj : Rectangle = Rectangle()

Constructores

En swift, una forma más correcta de llamar a los constructores sería: inicializadores.
Declaración del constructor (nótese que no se pone func en el constructor):

class Rectangle : GraphicObject {
    var width : Float = 0
    var height : Float = 0
    init( w : Float, h : Float ) {
        width = w
        height = h
    }
}

Cuando se instancia el objeto hay que pasar los argumentos que necesita el constructor (si no se pasan los argumentos adecuados el compilador da un error, además, los argumentos deben pasarse nombrados obligatoriamente):

var obj = Rectangle( w: 20.0, h: 10.0 )

Como se puede ver, los constructores no son funciones normales: no empiezan con func; hay que nombrar los argumentos obligatoriamente en la llamada; tienen un nombre especial (init).

Deinicializadores

Un deinicializador (destructor en otros lenguajes) se invoca justo antes de que el objeto se destruya. La destrucción del objeto ocurre cuando el recolector de basura así lo decide.

La sintaxis del deinicializador es especial, aunque es similar a la de un inicializador sin parámetros:

class Rectangle : GraphicObject {
    var width : Float = 0
    var height : Float = 0
    init( w : Float, h : Float ) {
        width = w
        height = h
    }
    deinit {
        // labores de limpieza del objeto
        println("cleanup")
    }
}

Llamada a métodos y acceso a propiedades

El acceso a las propiedades y métodos de un objeto es muy sencillo en swift (similar a otros lenguajes):

var a = obj.width
obj.width = 70.0
obj.show()

Para llamar a un método de clase:

Rectangle.getMaxWidth()

Propiedades almacenadas y calculadas

Las propiedades pueden ser de uno de los siguientes tipos: almacenadas o calculadas.
Las propiedades almacenadas son las que hemos visto hasta ahora, contienen su valor en una variable o una constante.
Una propiedad calculada es el resultado de un cálculo o una lógica. Para implementar variables calculadas tenemos que declarar un método getter y (opcionalmente) un método setter, para leer y escribir el valor de la variable respectivamente. La variable en sí misma podría incluso no existir.

class Rectangle : GraphicObject {
    .......
    var area : Float {
        get {
            return w * h
        }
    }
}

Se puede declarar igualmente el setter con la palabra reservada set.

class Rectangle : GraphicObject {
    .......
    var widthInInches : Float {
        get {
            return width / 2.54
        }
        set(inches) {
            width = inches * 2.54
        }
    }
}

Self

En swift, al igual que en otros muchos lenguajes orientados a objetos, desde dentro de los métodos de instancia se puede acceder al propio objeto. Para ello, se utiliza self (similar a this en otros lenguajes). No es obligatorio utilizar self, pero sí es recomendable para aclarar. Aunque en algunos casos sí es obligatorio, por ejemplo cuando algún nombre de parámetro de función coincido con el nombre de una propiedad:

    func setValues(width : Float, height : Float) {
        self.width = width
        slef.height = height
    }

Sobreescribir métodos

La herencia de clases supone que la clase hija hereda todas las propiedades y métodos de la clase padre, pero además, la clase hija puede añadir nuevas propiedades y métodos, ampliando así la funcionalidad de la clase padre. Pero otra manera de ampliar (o especializar) la funcionalidad de la clase padre es sobreescribiendo métodos en la clase hija:

override func show() {
    println("width=\(width), height=\(height)")
}

Desde un método sobreescrito se puede invocar al método original de la clase padre de la siguiente forma:

override func show() {
    super.show()
    println("width=\(width), height=\(height)")
}

Inicialización de subclases

La inicialización de un objeto se realiza en el inicializador de la clase, como ya hemos visto. En el caso de subclases, la clase hija es responsable de llamar al inicializador de la clase padre:

init( x: Float, y : Float, w : Float, h : Float ) {
    width = w
    height = h
    super.init(x : x, y : y)
}

La llamada al constructor de la clase padre se puede realizar en cualquier punto del inicializador de la clase hija (en algunos lenguajes se obliga a llamarlo al principio).




lunes, 17 de agosto de 2015

SWIFT (I) Tipos de datos, constantes y variables

Tipos de datos

Todas las constantes y variables swift tienen un tipo de datos. El programador puede especificar el tipo de datos de una constante o variable, o bien puede dejar que sea el compilador el que decida el tipo en función del valor que se asigna inicialmente a la constante o variable.

Enteros

Los enteros pueden almacenar números positivos o negativos sin decimales.
Swift proporciona los siguientes tipos enteros: con signo (Int8, Int16, Int32, Int64) y sin signo (UInt8, UInt16, UInt32, UInt64).También proporciona el tipo Int que el compilador asignará al tipo más adecuado para la plataforma. Apple recomienda utilizar el tipo Int, salvo que se desee una longitud específica.
Todos los tipos tienen las propiedades min y max para obtener los valores mínimo y máximo respectivamente de cada tipo.

Punto flotante

Los números en punto flotante pueden tener dígitos decimales.
Swift proporciona dos tipos de punto flotante: Float (32 bits, precisión 6 decimales) y Double (64 bits, precisión 15 decimales).

Boolean

Swift proporciona el tipo Bool con las constantes true y false para definir el tipos booleano.

Carácter

El tipo Character para almacenar un único carácter. Las variables carácter pueden tener un carácter imprimible como "A" o bien un carácter UNICODE: "\u{0058}"
Existen algunos caracteres especiales:

    var newline = "\n"
    var backslash = "\\"
    var doublequote = "\""
    var singlequote = "\'"

String

Un String es una cadena de caracteres.
Los String en swift se pueden construir utilizando "string interpolation":

    var message = "Juan tiene \(edad) años"

Variables

Las variables en swift se declaran mediante la palabra reservada var y se puede añadir opcionalmente el tipo de datos:

    var contador = 7
    var otroContador : Int = 67

Constantes

Una constante es similar a una variable, pero no se puede modificar.
Al igual que las variables, las contantes se declaran mediante la palabra reservada let y se puede añadir opcionalmente el tipo de datos:

    let maximo = 6757
    let nombre : String = "Valor"

Apple recomienda utilizar constantes siempre que sea posible, ya que su rendimiento es superior ligeramente al de las variables.

Tuplas

Las tuplas en swift permiten almacenar varios valores en una única entidad. Los items de una tupla pueden ser de cualquier tipo (dentro de una misma tupla, cada valor puede ser de un tipo diferente).
Por ejemplo:

    let tupla = ( 10, 4.56, "Nombre" )

Se accede a sus componentes por índice (empezando en cero):

    tupla.1  --> 4.56

Se pueden crear tuplas asignando un nombre a cada valor de la tupla:

    let tupla = ( contador : 7, media : 47.5, nombre : "Nombre" )

    tupla.media --> 47.5

Las tuplas son muy útiles para retornar múltiples valores desde una función.






SWIFT & iOS en Español


Swift

  1. Swift: Tipos de datos, constantes y variables
  2. Swift: Tipos optional
  3. Swift: Type cast y chequeo de tipos
  4. Swift: Operadores y expresiones
  5. Swift: Control de flujo
  6. Swift: Funciones y closures
  7. Swift: Clases y objetos
  8. Swift: Arrays y diccionarios

iOS

iOS (I) Autosizing Masks

En primer lugar quiero aclarar que las autosizing masks son el método "antiguo" de crear layouts multi-tamaño en iOS. Apple sacó los autolayout constraints posteriormente y es lo que debería utilizarse en lugar de las autosizing masks. Sin embargo, en mi opinión, es importante conocer y practicar las autosizing masks antes de meterse con autolayouts. Los autolayouts son mucho más genéricos, permiten definir prácticamente todo de forma gráfica y soportan casi todos los tamaños y orientaciones de pantalla.
Podríamos pensar que lo ideal es empezar a aprender autolayouts, ya que es "lo último", y las autosizing masks están "deprecated" por Apple. Y en parte eso sería lo correcto. Pero... yo he intentado aprender los autolayouts sin conocer previamente autosizing masks, y me ha resultado muy muy complicado. Por eso, recomiendo empezar por aquí, a pesar de que tenemos que ser conscientes que quizás este esfuerzo no valga para hacer muchas aplicaciones en el futuro. Para programadores que vienen de versiones de iOS anteriores y que ya conocen autosizing masks, pueden ir directamente a aprender los autolayouts sin problemas. Y por supuesto, como recomendación final antes de meternos en harina: para hacer aplicaciones, utilizar autolayouts constraints, sin duda.

Struts y Springs

Las autoresizing masks están basadas en los conceptos de layoyut "struts y springs"  (Ojo, no viene de los frameworks para aplicaciones web struts y spring).
Se define cómo cambia el tamaño y los márgenes de una vista cuando cambia el tamaño de la vista padre. O sea, para cada vista definimos qué márgenes (top, left, bottom, right) pueden cambiar y qué tamaño (horizontal, vertical) puede cambiar cuando la vista padre cambia de tamaño. Al ancho y alto se les llama springs, y a los márgenes se les llama struts.
Para cada margen o dimensión podemos definir si es fija o flexible, Si es fija se quedará como está cuando la vista padre cambie de tamaño, si es flexible cambiará de tamaño con el padre.
Cada restricción es una máscara (6 en total) y se combinan mediante OR para definir el comportamiento de cada vista ante los cambios de tamaño.
Estas son las máscaras:

  • UIViewAutoresizingFlexibleBottomMargin: permite que cambie la distancia entre el borde inferior de la vista y el borde inferior del padre.





  • UIViewAutoresizingFlexibleTopMargin: permite que cambie la distancia entre el borde superior de la vista y el borde superior del padre.




  • UIViewAutoresizingFlexibleLeftMargin: permite que cambie la distancia entre el borde izquierdo de la vista y el borde izquierdo del padre.




  • UIViewAutoresizingFlexibleRightMargin: permite que cambie la distancia entre el borde derecho de la vista y el borde derecho del padre.




  • UIViewAutoresizingFlexibleHeight: permite que cambie el alto de la vista cambie cuando cambie el alto de la vista padre.
  • UIViewAutoresizingFlexibleWidth: permite que cambie el ancho de la vista cambie cuando cambie el ancho de la vista padre.
Combinando estas máscaras con el operador de bit OR | se define el comportamiento de cada vista ante cambios en la vista padre.
NOTA: Cuando hablo de cambios en la vista padre, no me refiero a que en una aplicación la vista padre vaya a estar cambiando de tamaño (Normalmente, una vez arrancada la aplicación, las vistas no cambian su tamaño). Me refiero a que a partir del diseño con Interface Builder (Xcode), los tamaños de pantalla en los que va a correr nuestra aplicación cambiarán, y es a esos cambios a los que me refiero, a cambios con respecto al diseño de la aplicación. Por ejemplo, nosotros podríamos haber diseñado nuestra aplicación para orientación portrait, pero el usuario la ejecuta en modo landscape; entonces la vista principal cambia su tamaño (ancho > alto, y otros tamaños) y por tanto todas las vistas hijas recursivamente.

Ejemplo

Por ejemplo, imaginemos una vista de dimensiones (10, 10, 50, 50) (x,y,w,h) dentro de otra vista de dimensiones (0, 0, 70, 70).



A esta vista le ponemos la siguiente máscara:

    UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth

De esta forma, sea cual sea el tamaño de la vista padre, la distancia de los bordes a la vista padre será siempre 10 puntos. O sea, si la vista padre aumenta su ancho al doble, o sea 140 puntos, el resultado sería el siguiente:

Y si lo que aumenta es el alto de la vista padre:

Como se ve, al cambiar el tamaño de la vista padre, la distancia de los bordes de la vista a los bordes de la vista padre no cambian, ya que no hemos definido para esta vista ninguna máscara de flexibilidad en los bordes. Lo que sí hemos hecho flexible son el ancho y el alto, y por tanto, eso es lo que se redimensiona al cambiar el tamaño de la vista padre.
Igualmente, si el tamaño de la vista padre se redujera, la vista reduciría su tamaño, pero no cambiarían los márgenes, es decir, la distancia de los bordes.
Hay que tener en cuenta que cuando no se define una máscara, entonces esa máscara es fija, no flexible,

Otro ejemplo

Pongamos ahora la siguiente máscara a la vista anterior:

    UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin

En este segundo caso, hacemos flexible los márgenes inferior y derecho (o sea, la distancia de los bordes inferior y derecho a los mismos márgenes de la vista padre). El resto de márgenes y las dimensiones son fijas y no pueden cambiar.
Si en este caso la vista padre aumenta al doble su tamaño, tanto ancho como alto, tendremos:

Como se ve, las dimensiones de la vista no cambian. Tampoco cambian los márgenes izquierdo y superior. Lo que sí es flexible, y de hecho cambia son los márgenes derecho e inferior.

Autosizing masks en Xcode

Quizás el lector esté pensando ahora mismo cómo puede probar las autosizing masks, ya que Apple las ha sustituido por autolayout constraints. Sin embargo, Apple ha dejado una opción en los proyectos para poder hacer el layout de nuestras escenas utilizando autosizing masks. Lo más probable es que Apple ha dejado esta opción para mantener la compatibilidad con proyectos antiguos que utilizaban esta técnica, pero ahora mismo nos viene bien para poder practicarla y aprender.
Creemos un nuevo proyecto con Xcode, iOS -> Application -> Single View Application. Abrir Main.storyboard
En el panel de Utilities (activarlo con el icono de la derecha)
seleccionar el File inspector (el icono de la izquierda):

En la sección Interface builder document, desactivar las opciones:
  • Use Auto Layout
  • Use Size Classes
Con estas dos opciones desactivadas, nuestro storyboard funcionará con el sistema antiguo de autosizing masks.
Cuando intentemos desactivar la opción Use Auto Layout, Xcode nos pedirá confirmación y además nos indica que también se desactivará la opción Use Size Classes. Y más aún, al no tener size classes, Xcode necesita saber para qué tamaño/dispositivo vamos a hacer el diseño, iPhone o iPad (size classes permite definir el layout para múltiples dispositivos, como se verá en otro artículo). Seleccionemos por ejemplo la opción iPhone.
Ahora ya tenemos nuestro proyecto preparado para trabajar con autosizing masks, en lugar de los modernos autolayout constraints.

En la parte inferior del panel de utilities, seleccionar la object library (el tercer icono):

Buscar el objeto View (Represents a rectangular region in which it draws and receives events) y arrastrarlo a la vista. Lo primero que haremos será cambiarle el color para poder ver cómo cambia y darle una posición y tamaño dentro de la vista padre.
Para cambiarle el color, seleccionar el cuarto icono:
y después cambiar Background.
Para situarlo y darle tamaño, seleccionar el quinto icono. El iPhone 5 tiene un tamaño de 320x568 puntos, así que a nuestra vista le daremos un tamaño algo más pequeño, por ej. (10, 10, 300, 500).
En la sección de autoresizing, quitar todas las máscaras. Por defecto, aparecerán todas activadas (en rojo en los bordes y dentro del recuadro). Para desactivarlas, pulsar sobre cada una de las 6 para que queden en un color muy clarito.
Vamos a previsualizar nuestra vista con distintos tamaños de la vista padre (el dispositivo en este caso). Para ello, tenemos la opción clásica de arrancar el simulador y ejecutar con distintos tipos de dispositivos. Pero esta opción es bastante engorrosa y lenta, ya que hay que cambiar el dispositivo, compilar, y ejecutar la aplicación. Xcode tiene una función de previsualización de una escena en el mismo Interface Builder, sin tener que arrancar el simulador. Veamos cómo hacerlo.
En primer lugar, abrir el asistente (el segundo icono con dos círculos):
Normalmente aparecerá el código swift del controlador asociado a la vista. Pero esto no es lo que queremos. En la parte de arriba de la ventana de código swift, tenemos una especie de pestaña con dos círculos y la etiqueta Automatic. Pulsar esa pestaña y en el menú que aparece, seleccionar Preview. Esto nos cambiará la vista del asistente a una previsualización. En la parte inferior izquierda hay un botón + para añadir nuevas previsualizaciones (nótese que son previsualizaciones de iPhone, no de iPad, ya que al desactivar size classes, hemos elegido el iPhone como dispositivo para nuestra aplicación). Cuando tengamos varias previsualizaciones es muy útil poder moverse por esta pantalla (nótese que no hay barras de scroll). Para ello, se utiliza la rueda del ratón (scroll hacia arriba y hacia abajo) y tecla SHIFT más rueda del ratón (scroll horizontal, izquierda y derecha). Si pulsamos sobre una de las previsualizaciones, podemos borrarla pulsando la tecla de borrar. Además, en cada previsualización podemos ver su versión landscape. Para ello, seleccionamos una previsualización (excepto la primera, que no se puede ni borrar ni rotar) y en la parte de abajo de la previsualización tenemos un botón que permite rotar el dispositivo.

Una vez que sabemos manejar el asistente de previsualizaciones, estamos en condiciones de ver cómo se comporta nuestra vista cuando por ejemplo, rotamos el teléfono. Y como podremos ver, el resultado es desastroso (si hemos quitado todas las autosizing masks). Pero podemos arreglarlo fácilmente: seleccionamos nuestra vista (no la previsualización), elegimos el inspector de tamaños (size inspector) y activamos las dos máscaras internas (width y height) en la sección de autoresizing. Sólo con estos dos cambios, nuestra vista crecerá y decrecerá para adaptarse al tamaño del dispositivo, pero manteniendo los márgenes fijos.

Ahora es el momento de que el lector se ponga manos a la obra y practique con distintos layouts, colocando varias vistas, unas dentro de otras y probando distintas máscaras. Éste es un ejercicio muy útil y que recomiendo (al menos durante una hora o dos) para hacerse con esta técnica (que vuelvo a insistir, está obsoleta hoy). Si el lector se plantea algunos problemas un poco complejos se dará cuenta de que hay algunas posibilidades de layout que son imposibles de llevar a cabo con autoresizing masks. En estos casos sería necesario meter código que trate los eventos de cambio de orientación y cambiar los tamaños y posiciones (esto es algo muy engorroso y que se resuelve con autolayouts, como veremos en otro artículo).

Ejercicio

Aquí propongo a modo de ejercicio, un layout para que el lector practique, aunque creo que es bueno intentar imaginar una aplicación y diseñar su layout con Xcode utilizando autoresizing masks.



Para terminar

Una vez más insisto que las autosizing masks no es la técnica recomendada por Apple hoy día (2015), sino los autolayouts junto con sizing classes.
Pero, ¿cuál es el motivo por el que Apple decidió dejar de utilizar autosizing masks? Realmente, los motivos de Apple no siempre son claros, pero en este caso sí hay razones técnicas que permiten explicar el cambio. Hasta hace poco, no era difícil diseñar una aplicación que funcionara en todos los dispositivos iOS existentes, simplemente había que pensar en iPhone portrait y landscape, iPad portrait y landscape. En total 4 posibilidades. Lo que solían hacer los desarrolladores era hacer 4 storyboards, uno para cada una de estas posibilidades.
Cuando Apple sacó los nuevos modelos de iPhone, el 6 y sobre todo el 6+, el espectro de posibilidades empezó a aumentar, y el futuro nos depara algunas otras posibilidades (por ejemplo, el iPad Pro, que parece que está al caer, aunque ahora mismo son sólo rumores). Esto podría complicar muchísimo el diseño de aplicaciones. Así que parece que Apple decidió abordar definitivamente el problema de múltiples tamaños y resoluciones en los dispositivos (cosa que por cierto, Google abordó desde el principio con Android).
El problema de autosizing masks es que no soporta todas las posibilidades de layout (no es difícil plantearse un ejemplo imposible de realizar), al menos con Interface Builder directamente (sería necesario resolver algunos temas por código). Otro problema es que para cada posibilidad (iPhone, iPad, portrait, landscape, etc) tenemos que hacer un diseño especial (no para casos sencillos, pero sí en la mayoría de los casos).
Para resolver todos estos problemas, Apple "inventó" los autolayouts y los size classes que veremos en un artículo posterior.







martes, 11 de agosto de 2015

SWIFT (VI) FUNCIONES Y CLOSURES

Las funciones son algo típico en todos los lenguajes de programación clásicos. Los closures ya no lo son tanto, al menos, no son tan conocidos, aunque llevan existiendo muchos años en lenguajes tan conocidos como Javascript.

FUNCIONES EN SWIFT

Una función es un bloque de código que puede ser invocado por su nombre para ejecutar una determinada tarea. Puede recibir datos de entrada y puede retornar uno o más resultados. Cuando se define una función se especifican los parámetros de entrada y el tipo del valor retornado (si los hay). Cuando se invoca a una función se pasan datos como argumentos a la función y se puede recoger el resultado en una variable, por ejemplo. Nótese la sutil diferencia entre parámetros y argumentos.

Ejemplo de declaración de una función en swift:

    func suma(operando1 : Int, operando2 : Int) -> Int {
        return operando1 + operando2
    }

Llamada a una función:

    var a : Int = suma(3, 6)

Parámetros externos

En el ejemplo anterior, los nombres de los parámetros son internos a la función y no pueden utilizarse en la llamada, es decir, la llamada se basa únicamente en el orden de los parámetros. Pero a veces es mejor (o más claro) que en la llamada a la función se especifique también el nombre de los parámetros. Para ello se antepone el carácter # al nombre del parámetro:

    func resta(#minuendo : Int, #sustraendo : Int) -> Int {
        return minuendo - sustraendo
    }

Y la llamada:

    var resultado : Int = resta( minuendo : 5, sustraendo : 3)

Parámetros por defecto

Swift permite especificar un valor por defecto para un parámetro (o parámetros) que pudieran no especificarse en la llamada a función. Estos parámetros con valores por defecto deben situarse al final de la lista de parámetros en la declaración de la función. Cuando en un parámetro se especifica un valor por defecto, swift asume que ese parámetro es externo y permite su nombrado en la llamada (ver subapartado anterior "parámetros externos").

    func info(account : Int, owner : String  = "Unknown") -> String {
        return "Account: \(account), owner: \(owner)."
    }

Ejemplos de posibles llamadas a esta función:

    var s1 = info(26351)  --> Account 26351, owner: Unknown.
    var s2 = info(10005, owner : "John")  --> Account 10005, owner: John.

Retornando múltiples valores desde una función

Una función puede retornar múltiples valores de varias formas: retornando un objeto, retornando un array o diccionario, y por último, retornando una tupla. Veamos este último caso.

    func getUserInfo(id : String) -> (nombre : String, apellido : String, dni : Int) {
        // buscar información del usuario en BD por ejemplo
        return ("Juan", "López", "4747563-P")
    }

Llamada a esta función:

    var info = getUserInfo("A3625")
    println("\(info,nombre) \(info,apellido) - \(info.dni)")

Número variable de parámetros

En swift una función puede recibir un número variable de parámetros (puede ser cero). Esto se indica con ...
Dentro de la función los parámetros son un array.

    func printInfo(datos : String...) {
        for s in datos {
            println(s)
        }
    }

Llamada:

    printInfo("a", "b", "c", "d")

Parámetros variables

Los parámetros recibidos por una función son constantes (es como si tuvieran un let delante) y no se pueden modificar dentro de la función.
Si queremos modificar alguno de los parámetros de una función, debemos declararlo variable poniéndole var delante.

    func suma(var operando1 : Int, operando2 : Int) -> Int {
        operando1 += operando2
        return operando1
    }

Esto no significa que el argumento pasado a la función se vaya a modificar, simplemente que podemos cambiar el valor internamente en la función. Para que se modifique el argumento externo, tendremos que utilizar parámetros de entrada/salida, como se describe en el siguiente subapartado.

Parámetros de entrada/salida

Para que una función pueda modificar el valor de un argumento es necesario definir el parámetro correspondiente como inout:

    func duplica(inout valor : Int) {
        valor = 2 * valor
    }

En la llamada tenemos que anteponer el carácter & al argumento (el cual tiene que ser una variable obligatoriamente):

    var v : Int = 4;
    duplica(&v)
    println(v)   --> 8

CLOSURES EN SWIFT

Antes de entrar en las closures, introduzcamos el tema 

Las funciones como tipos de datos

Una característica muy interesante de swift (y de otros lenguajes de programación) es que las funciones pueden ser tratadas como tipos de datos. De esta forma, se puede por ejemplo definir una función y asignarla a una constante o variable:

    func kilometers2Miles(kilometers : Float) -> Float {
        return meters * 0.62137
    }
    let f = kilometers2Miles

Habiendo hecho esa asignación podemos invocar a la función a través de la constante:

    var result = f(100.0)

Lo interesante de esto es que cuando asignamos una función a una constante tenemos todas las capacidades de los tipos de datos. Concretamente, una función puede pasarse como argumento a otra función, o incluso ser retornada de una función.

El tipo de datos de la función lo definen los parámetros y el valor retornado. En el ejemplo anterior, la función recibe un parámetro Float y devuelve un valor Float, por tanto el tipo de esta función sería:

    (Float) -> Float

Por ejemplo, podemos definir una función que reciba otra función como parámetro:

    func invoke( theFunction : (Float) -> Float, value : Float) -> Float {
        return theFunction(value)
    }

Y podemos invocarla así:

    invoke(kilometers2Miles, 34.8)

Igualmente, podemos retornar una función:

    func getFunction() -> (Float) -> Float {
        return kilometers2Miles
    }

Expresiones closure

Una expresión closure es sencillamente un bloque de código. Las expresiones closure se pueden asignar a constantes o variables:

    let f = { println("Hola") }
    f()   --> Hola

Las expresiones closure también pueden recibir parámetros y devolver resultados. Por ejemplo:

    let m = { ( operando1 : Float, operando2 : Float) -> Float in return operando1 * operando2 }
    let v = m(20, 34.2)

La sintaxis es parecida a la de una función, pero no tiene nombre, todo va entre llaves y se utiliza la palabra reservada in para indicar el comienzo del código. De hecho, las funciones swift son expresiones closure con nombre.

Closures

Un closure es un bloque de código (una función o una expresión closure) junto con una o más variables que existen en el contexto de ese bloque de código. Por ejemplo:

    func externa() -> () -> Int {
        var contador = 0
        func interna() -> Int {
            contador += 1
            return contador
        }
        return interna
    }

    let miClosure = externa()
    let resultado = miClosure()

En este ejemplo, la función externa retorna una función interna. Realmente retorna un closure, ya que retorna la función interna más la variable contador, que está declarada en el scope de la función interna. De hecho, cada vez que se llama a miClosure() se va incrementando el contador.












domingo, 9 de agosto de 2015

SWIFT (III) TYPE CAST Y CHEQUEO DE TIPOS

TYPE CASTING

Hay dos tipos de conversión de tipos en swift:
  • upcasting: se trata de convertir un objeto de una clase a otra clase padre (es decir, subir hacia arriba en la jerarquía)
  • downcasting: se trata de convertir un objeto de una clase a otra clase derivada (es decir, bajar en la jerarquía)
El upcasting es siempre seguro (ya que siempre podemos convertir un objeto de una clase a una de sus clases padre) y el compilador lo deja hacer sin problemas. Ejemplo de upcasting:

        var b : UIButton = UIButton()
        var c : UIControl = b as UIControl

En este ejemplo, como UIControl es una clase padre de UIButton, podemos hacer tranquilamente el casting (upcasting) de UIButton a UIControl.

Sin embargo, el downcasting es responsabilidad del programador, es decir, el compilador no puede asegurarlo, y por tanto, el lenguaje swift tiene una syntaxis especial para el downcasting. Cuando se hace downcasting utilizando as, el compilador da un error avisando que no puede convertir el tipo:

        var c : UIControl = UIButton()
        var b : UIButton = c as UIButton    --> error de compilación

La primera sentencia es correcta, sin embargo, la segunda dará un error de compilación advirtiendo que no puede convertir los tipos de forma segura. Ese error no significa que se esté haciendo algo incorrecto, sólo significa que el compilador no puede asegurar la conversión.

El downcasting en swift se hace utilizando un casting forzado con as!

        var c : UIControl = UIButton()
        var b : UIButton = c as! UIButton   --> correcto

En este caso se deja al programador la responsabilidad de que c sea de la clase UIButton o una de sus clases derivadas. Si no fuera así, se elevaría una excepción en runtime (ver más abajo cómo chequear el tipo de una variable).

Existe una tercera forma de casting en swift: optional binding. Para ello se emplea as?

En este caso, la conversión retorna un optional, y si no es posible la conversión, retornará nil. Por ejemplo:

        var b : UIButton? = c as? UIButton   --> si c no puede convertirse a UIButton, entonces el resultado es nil, lo cual es válido porque la variable b es optional

Igualmente podemos emplear esta técnica en un if como se hace con los tipos optional:

        if let b = c as? UIButton {
                b puede utilizarse como un UIButton
        }

TYPE CHECKING

Además del casting, en swift es posible chequear el tipo de una variable:

        if b is UIButton {
                // b es una instancia de UIButton y por tanto podemos utilizarlo como tal
                c = b as! UIButton   --> esto va a funcionar
        }

Combinando todas estas técnicas podemos manipular el tipo de las variables dentro de su jerarquía.













SWIFT (II) TIPOS OPTIONAL

Swift es un lenguaje de programación moderno que utiliza las mejores técnicas de los lenguajes de programación que han tenido más éxito en los últimos años: Java, Objective-C, Python, etc.
Este nuevo lenguaje de programación "inventado" por Apple para que sea su lenguaje del futuro en plataformas iOS y OSX tiene muchísimas ventajas, porque ha cogido lo mejor de cada lenguaje, convirtiéndose hoy en día (2015) en el lenguaje más seguro y potente que existe.
Sin embargo, hay ciertas características de swift que no han sido "copiadas" de otros lenguajes y que suponen una barrera de entrada al lenguaje, incluso para programadores muy experimentados. Una de esas barreras son los tipos optional, algo que bloquea a muchos programadores cuando empiezan con swift (conozco a algún programador que empezó con mucha ilusión a aprender swift y cuando se encontró con los tipos optional, después de dar muchas vueltas al tema, no consiguió entenderlo y abandonó el lenguaje "para otro momento").
Voy a intentar explicar (en español y con ejemplos) los tipos optional de swift para que no sean una barrera para nadie. Supongo que se me quedarán algunas cosas en el tintero, y también supongo que cuando el lector pase por este blog, el lenguaje swift habrá cambiado bastante y que algunas de las cosas que aquí expongo podrían haber cambiado (prometo mantener esta entrada al día en la medida de lo posible). 

TIPOS OPTIONAL

Los lenguajes de programación permiten expresar la situación en la que una variable no contiene ningún valor. Esto es muy útil, por ejemplo, cuando queremos retornar nil (o null) desde una función indicando que no se retorna nada. También es útil para indicar que una variable todavía no se ha inicializado. En lenguajes como C++ o Java, para indicar que una variable no contiene ningún valor, se asigna el valor nil a la variable. Esta solución funciona para variables de tipo objeto, pero no para los tipos básicos: int, double, boolean, etc. En estos casos teníamos que asignar valores especiales para indicar que la variable no tiene valor, por ejemplo -1. Swift resuelve este problema de manera general para todos los tipos mediante los tipos optional.

En Swift se definen los tipos optional añadiendo el carácter ? al final del tipo. Ejemplos:

        Int?
        String?
        MiClase?
        etc

En Swift, una variable de un tipo optional puede contener un valor (básico u objeto, según el tipo) o bien no contener ningún valor. Sólo se puede asignar nil a una variable de un tipo optional. A una variable que no sea optional (sin el carácter ?), no se le puede asignar nil, ya que el compilador daría error:

        var i : Int? = nil
        var j : Int = nil   --> error de compilación

Igualmente si una variable es de tipo optional, no podemos asignarla a otra variable que no sea optional (incluso aunque contenga un valor).

        var k : Int? = 7
        var m : Int = k   --> error de compilación

Tenemos que pensar que los tipos Int e Int? no son lo mismo. El tipo Int? es un wrapper de Int, es decir, una especie de objeto que contiene un Int, por tanto, no podemos asignar un Int? a un Int. Sin embargo, sí es posible asignar un Int a un Int?, ya que el compilador hace las comversiones adecuadas. En el siguiente apartado veremos cómo asignar una variable Int? a otra de tipo Int.

En resumen, cualquier tipo Swift puede ser optional, tanto los objetos como los tipos básicos. Y sólo podremos asignar nil a una variable de un tipo optional.

FORCED UNWRAPPING

Forced unwrapping es el mecanismo que proporciona swift para asignar una variable de un tipo optional a una variable no optional. Forced unwrapping permite obtener la variable interna (si existe) que tiene el wrapper optional. Por ejemplo:

        var a : Int? = 5
        var b : Int = a!

Añadiendo el carácter ! al final de la variable se hace un forced unwrapping de la variable optional, es decir, se extrae el valor de la variable si existe (si la variable no contuviera un valor, o sea nil, se produciría una excepción en runtime).

Cuando se va a hacer forced unwrapping Para comprobar si el valor de una variable es conveniente comprobar primero si la variable optional es distinta de nil, es decir, si tiene un valor. Para ello se compara utilizando los operadores == y != (habituales en lenguajes de programación tipo C). Si hemos comprobado que una variable es != nil porque hemos hecho un if previamente, entonces podríamos leer su valor con tranquilidad:

        if a != nil {
                b = a!
                print("Se puede leer el valor de a = \(a!) sin problemas")
        } else {
                print("La variable a no contiene ningún valor")
        }

OPTIONAL BINDING

Optional binding es una técnica de swift que permite, al mismo tiempo que se comprueba que una variable optional tiene un valor, asignar ese valor a otra variable (o constante) para utilizarla después sin necesidad de forced unwrapping.

La sintaxis del optional binding es:

        var a : Int? = 4
        if let b = a {
                print("Se puede leer el valor de b = \(b) sin problemas")
        } else {
                print("La variable a no contiene ningún valor")
        }

Se pueden utilizar constantes (let) o variables (var) en optional binding, dependiendo de si el valor se va a modificar después o no.

También se pueden crear varias variables y constantes en un optional binding:

        if let a1 = a, b1 = b {

IMPLICIT UNWRAPPED OPTIONALS

Algunas veces, tenemos claro que una variable optional va a tener siempre un valor (es decir, el programador sabe que esa variable siempre va a contener un valor). En estos casos puede ser útil indicar en la declaración este hecho. Para ello, se utiliza la siguiente sintaxis:

        var i : Int!  --> se inicializa a nil
        i = 5

Este tipo de optionals se llaman implicit unwrapped optionals, indicando que la variable es optional pero el compilador va a hacer un unwrapped de forma implícita cada vez que se utilice. Por ejemplo:

        var j = i   --> no hace falta forced unwrapping

Lo primero que nos preguntamos tras leer estas líneas es: Y entonces, ¿por qué no declaramos la variable como no optional? Es la pregunta obvia después de la definición de implicit unwrapped optional. Veamos un caso útil en un constructor de una clase:

        class Ciudad {
                var nombre : String!    --> se inicializa a nil, por tanto tiene que ser optional
                init( nombreCiudad:String) {
                        self.nombre = nombreCiudad   --> se le asigna un valor y ya nunca va a ser nil
                }
        }

A partir de ese momento, la variable city.nombre se puede utilizar normalmente, sin hacer forced unwrapping. Es como si dejáramos en la responsabilidad del programador asegurar que una variable nunca va a ser nil (aunque para el compilador, sí podemos asignarle un valor nil).

Alguien podría decir ahora: "Yo tengo una solución más sencilla a este caso"

        class Ciudad {
                var nombre : String = ""  --> no tiene porqué ser optional
                init( nombreCiudad:String) {
                        self.nombre = nombreCiudad
                }
        }

Y tendría razón, así resolvemos también el problema. Pero podríamos complicar el caso un poco más y la solución ya no sería tan sencilla:

        class Ciudad {
                var elAlcalde : Alcalde!    --> se inicializa a nil
                init( nombreAlcalde:String) {
                        self.elAlcalde = Alcalde(nombreAlcalde)
                }
        }

Una variable implicit unwrapped optional es realmente una variable optional (por debajo) pero se puede utilizar como una variable no optional sin necesidad de hacer unwrapping cada vez que se usa. Esta es la diferencia:

        let a : String? = "Hola"
        var a1 = a!   --> es necesario forced unwrapping

        let b : String! = "Adiós"
        var b1 = b  --> se accede normalmente, a pesar de que b es optional

Con las variables implicit unwrapped optional estamos dando permiso al compilador para que haga un unwrap automáticamente cada vez que se utiliza. Es nuestra responsabilidad asegurar que la variable contiene un valor (si no lo tuviera, se elevaría una excepción de runtime).

Es posible también hacer un chequeo del valor de la variable cuando no estemos seguros:

        var a : String! = "hola"
        if a != nil {
                var b = a
        }

Yo creo que las variables implicit unwrapped optional son muy similares a las variables objeto de otros lenguajes, donde es nuestra responsabilidad comprobar previamente si tienen un valor, y si no lo tienen, se eleva una excepción.

CONCLUSIÓN

Apple recomienda utilizar variables optional ? cuando no estemos seguros de que la variable siempre va a contener un valor. Si estamos seguros de que siempre va a contener un valor, usemos implicit unwrapped optional !. Y si se inicializa con un valor y siempre va a tenerlo, entonces usaremos variables no optional. Éste sería un resumen final de todo este capítulo dedicado al novedoso tema de las variables optional en swift.

Si tenéis comentarios, sugerencias, habéis visto errores, por favor, no dudéis en poner un comentario en esta entrada de blog.











viernes, 9 de enero de 2015

Javascript avanzado: Funciones

Este es el primer artículo de una serie que tengo la intención de escribir sobre javascript avanzado. Empezaré por intentar explicar algunos conceptos avanzados de las funciones en javascript.

Muchos desarrolladores, cuando les preguntan ¿sabes javascript? Responden un sí inmediata y contundentemente. Yo era uno de ellos, hasta hace poco.
Muchos pensábamos que javascript se reduce a una serie de tipos básicos, condiciones if, bucles for/while y declaración de funciones. Algunos incluso hemos utilizado callbacks para eventos en páginas html.
También, muchos pensábamos que javascript se utiliza única y exclusivamente en el desarrollo web, dentro las páginas html. Efectivamente, así empezó javascript. Pero hoy en día, esto no es cierto. Javascript es un lenguaje muy avanzado y que se utiliza en muchos otros entornos además del desarrollo web, por ejemplo, en el desarrollo de aplicaciones para móvil.

Javascript es uno de los lenguajes más avanzados y expresivos que existen actualmente (esto puede sorprender a más de uno). Estas son algunas de sus características destacadas:

  • Rendimiento: Javascript utiliza compilación just-in-time, o sea, se compila y se traduce a código máquina justo antes de ejecutarse. Su rendimiento es similar al de aplicaciones escritas en C o C++. Sin embargo, javascript tiene la penalización de la recolección de basura y enlazado dinámico de tipos (las variables pueden cambiar de tipo dinámicamente).
  • Objectos: Javascript tiene características avanzadas de orientación a objetos. Utiliza el modelo de herencia con prototipos (en lugar de clases, tenemos prototipos de objetos). Este modelo es más potente y flexible que el modelo de herencia con clases utilizado en lenguajes como Java. Javascript también soporta, a pesar de ser poco conocidas, las siguientes características: encapsulación, polimorfismo, herencia múltiple y composición.
  • Sintaxis: La sintaxis de javascript es similar a la de lenguajes como C++, Java, C# o PHP. Esta es una de las causas del éxito de javascript.
  • Funciones: En javascript, casi todo son objetos, incluidas las funciones. Por este motivo, podemos usar una función en cualquier sitio en que podemos utilizar una variable (por ejemplo en un parámetro de una llamada a función). Las funciones que reciben como parámetro una función o retornan una función como resultado, se llaman funciones de alto orden.
  • Reutilización: Javascript es portable, es decir, puede ejecutarse en cualquier plataforma.
Comencemos por tanto explicando algunos conceptos, no demasiado avanzados sobre las funciones en javascript, pero que introducen los conceptos más avanzados que se explicarán posteriormente. En posteriores artículos hablaré del ámbito (scope), closures, this, prototype.

Las funciones son datos

En javascript existen los siguientes tipos:

  • PRIMITIVOS
    • Number: desde 5e-308 hasta 1e+308, Infinity, NaN
    • Boolean: true, false
    • String: "....", '....'
    • undefined: cuando una variable se declara, su valor es undefined
    • null: se puede asignar null a una variable
  • OBJECT
    • Object
    • function

Las funciones son también un dato, concretamente, Object.

var f = function(v) {
    return v+1;
}
typeof f;   // "function"


Funciones de callback

function add(f1, f2) {
    return f1() + f2();
}
var one = function() { return 1; }
var two = function() { return 2; }
add(one, two);   // 3

Funciones inmediatas

Las funciones inmediatas se ejecutan inmediatamente:

(function() {
    alert('hello');
})();

Las funciones inmediatas pueden recibir parámetros:

(function(name) {
    alert('hello '+name);
})('world');
Las funciones inmediatas se pueden utilizar para tareas de inicialización, ya que se ejecutan inmediatamente y todas sus variables locales quedan dentro, en privado.

Funciones privadas

function outer(p1) {
    function inner(p2) {
        return 2 * p2;
    }
    return 10 + inner(p1);
}

La función inner() no es accesible desde fuera.

Función que retorna una función

function a() {
    return function() { return 'A'; };
}

var f = a();   // asigna la función interna a la variable f
f();       // invoca la función interna

También se puede invocar la función interna del siguiente modo:

a()();
Se puede reasignar una función. De esta forma, la primera vez que se ejecuta la función puede realizar labores de inicialización y después su función habitual:

function f() {
    // labores iniciales
    .......
    return function() { ...... labores habituales .... };
}
f = f(); 
   
Otra forma más elegante de reasignar una función:

function a() {
    ... código que se ejecuta la primera vez
    a = function() { ... código habitual .... };
}
Y otra forma más, utilizando funciones inmediatas:

var a = (function() {
    function init() {
        .... inicialización
    }
    function work() {
        .... trabajo real
    }
    init();
    return work;
})();