Comparte esta página

Aprende X en Y minutos

Donde X=swift

Swift es un lenguaje de programación para el desarrollo en iOS y macOS creado por Apple. Diseñado para coexistir con Objective-C y ser más resistente contra el código erroneo, Swift fue introducido en el 2014 en el WWDC, la conferencia de desarrolladores de Apple.

Véase también la guía oficial de Apple, getting started guide, el cual tiene un completo tutorial de Swift.

// Importar un módulo
import UIKit

//
// MARK: Básicos
//

// XCode soporta referencias para anotar tu código y agregarlos a lista de la
// barra de saltos.
// MARK: Marca de sección
// TODO: Hacer algo pronto
// FIXME: Arreglar este código

// En Swift 2, println y print fueron combinados en un solo método print.
// Print añade una nueva línea automáticamente.
print("Hola, mundo") // println ahora es print
print("Hola, mundo", appendNewLine: false) // print sin agregar nueva línea

// Valores de variables (var) pueden cambiar después de ser asignados
// Valores de constrantes (let) no pueden cambiarse después de ser asignados

var myVariable = 42
let øπΩ = "value" // nombres de variable unicode
let π = 3.1415926
let convenience = "keyword" // nombre de variable contextual
// Las declaraciones pueden ser separadas por punto y coma (;)
let weak = "keyword"; let override = "another keyword"
// Los acentos abiertos (``) permiten utilizar palabras clave como nombres de
// variable
let `class` = "keyword"
let explicitDouble: Double = 70
let intValue = 0007 // 7
let largeIntValue = 77_000 // 77000
let label = "some text " + String(myVariable) // Conversión (casting)
let piText = "Pi = \(π), Pi 2 = \(π * 2)" // Interpolación de string

// Valores específicos de la compilación (build)
// utiliza la configuración -D
#if false
    print("No impreso")
    let buildValue = 3
#else
    let buildValue = 7
#endif
print("Build value: \(buildValue)") // Build value: 7

/*
    Las opcionales son un aspecto del lenguaje Swift que permite el
    almacenamiento de un valor `Some` (algo) o `None` (nada).

    Debido a que Swift requiere que cada propiedad tenga un valor,
    hasta un valor 'nil' debe de ser explicitamente almacenado como un
    valor opcional.

    Optional<T> es un enum.
*/
var someOptionalString: String? = "opcional" // Puede ser nil
// Al igual que lo anterior, pero ? es un operador postfix (sufijo)
var someOptionalString2: Optional<String> = "opcional"

if someOptionalString != nil {
    // No soy nil
    if someOptionalString!.hasPrefix("opt") {
        print("Tiene el prefijo")
    }

    let empty = someOptionalString?.isEmpty
}
someOptionalString = nil

// Opcional implícitamente desenvuelto
var unwrappedString: String! = "Un valor esperado."
// Al igual que lo anterior, pero ! es un operador postfix (sufijo)
var unwrappedString2: ImplicitlyUnwrappedOptional<String> = "Un valor esperado."

if let someOptionalStringConstant = someOptionalString {
    // tiene valor `Some` (algo), no nil
    if !someOptionalStringConstant.hasPrefix("ok") {
        // No tiene el prefijo
    }
}

// Swift tiene soporte de almacenamiento para cualquier tipo de valor.
// AnyObject == id
// A diferencia de Objective-C `id`, AnyObject funciona con cualquier
// valor (Class, Int, struct, etc)
var anyObjectVar: AnyObject = 7
anyObjectVar = "Cambiado a un valor string, no es buena práctica, pero posible."

/*
    Comentar aquí

    /*
        Comentarios anidados también son soportados
    */
*/

//
// MARK: Colecciones
//

/*
    Tipos Array (arreglo) y Dictionary (diccionario) son structs (estructuras).
    Así que `let` y `var` también indican si son mudables (var) o
    inmutables (let) durante la declaración de sus tipos.    
*/

// Array (arreglo)
var shoppingList = ["catfish", "water", "lemons"]
shoppingList[1] = "bottle of water"
let emptyArray = [String]() // let == inmutable
let emptyArray2 = Array<String>() // igual que lo anterior
var emptyMutableArray = [String]() // var == mudable


// Dictionary (diccionario)
var occupations = [
    "Malcolm": "Captain",
    "kaylee": "Mechanic"
]
occupations["Jayne"] = "Public Relations"
let emptyDictionary = [String: Float]() // let == inmutable
let emptyDictionary2 = Dictionary<String, Float>() // igual que lo anterior
var emptyMutableDictionary = [String: Float]() // var == mudable


//
// MARK: Flujo de control
//

// Ciclo for (array)
let myArray = [1, 1, 2, 3, 5]
for value in myArray {
    if value == 1 {
        print("Uno!")
    } else {
        print("No es uno!")
    }
}

// Ciclo for (dictionary)
var dict = ["uno": 1, "dos": 2]
for (key, value) in dict {
    print("\(key): \(value)")
}

// Ciclo for (range)
for i in -1...shoppingList.count {
    print(i)
}
shoppingList[1...2] = ["steak", "peacons"]
// Utilizar ..< para excluir el último valor

// Ciclo while
var i = 1
while i < 1000 {
    i *= 2
}

// Ciclo do-while
do {
    print("Hola")
} while 1 == 2

// Switch
// Muy potente, se puede pensar como declaraciones `if` con _azúcar sintáctico_
// Soportan String, instancias de objetos, y primitivos (Int, Double, etc)
let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
    let vegetableComment = "That would make a good tea sandwich."
case let localScopeValue where localScopeValue.hasSuffix("pepper"):
    let vegetableComment = "Is it a spicy \(localScopeValue)?"
default: // obligatorio (se debe cumplir con todos los posibles valores de entrada)
    let vegetableComment = "Everything tastes good in soup."
}


//
// MARK: Funciones
//

// Funciones son un tipo de primera-clase, quiere decir que pueden ser anidados
// en funciones y pueden ser pasados como parámetros

// Función en documentación de cabeceras Swift (formato reStructedText)

/**
    Una operación de saludo

    - Una viñeta en la documentación
    - Otra viñeta en la documentación

    :param: name Un nombre
    :param: day Un día
    :returns: Un string que contiene el valor de name y day
*/
func greet(name: String, day: String) -> String {
    return "Hola \(name), hoy es \(day)."
}
greet("Bob", "Martes")

// Similar a lo anterior, a excepción del compartamiento de los parámetros
// de la función
func greet2(requiredName: String, externalParamName localParamName: String) -> String {
    return "Hola \(requiredName), hoy es el día \(localParamName)"
}
greet2(requiredName:"John", externalParamName: "Domingo")

// Función que devuelve múltiples valores en una tupla
func getGasPrices() -> (Double, Double, Double) {
    return (3.59, 3.69, 3.79)
}
let pricesTuple = getGasPrices()
let price = pricesTuple.2 // 3.79
// Ignorar tupla (u otros) valores utilizando _ (guión bajo)
let (_, price1, _) = pricesTuple // price1 == 3.69
print(price1 == pricesTuple.1) // true
print("Gas price: \(price)")

// Cantidad variable de argumentos
func setup(numbers: Int...) {
    // Es un arreglo
    let number = numbers[0]
    let argCount = numbers.count
}

// Pasando y devolviendo funciones
func makeIncrementer() -> (Int -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

// Pasando como referencia
func swapTwoInts(inout a: Int, inout b: Int) {
    let tempA = a
    a = b
    b = tempA
}
var someIntA = 7
var someIntB = 3
swapTwoInts(&someIntA, &someIntB)
print(someIntB) // 7


//
// MARK: Closures (Clausuras)
//
var numbers = [1, 2, 6]

// Las funciones son un caso especial de closure ({})

// Ejemplo de closure.
// `->` Separa los argumentos del tipo de retorno
// `in` Separa la cabecera del cuerpo del closure
numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
})

// Cuando se conoce el tipo, como en lo anterior, se puede hacer esto
numbers = numbers.map({ number in 3 * number })
// o esto
//numbers = numbers.map({ $0 * 3 })

print(numbers) // [3, 6, 18]

// Closure restante
numbers = sorted(numbers) { $0 > $1 }

print(numbers) // [18, 6, 3]

// Bastante corto, debido a que el operador < infiere los tipos

numbers = sorted(numbers, < )

print(numbers) // [3, 6, 18]

//
// MARK: Estructuras
//

// Las estructuras y las clases tienen capacidades similares
struct NamesTable {
    let names = [String]()

    // Subscript personalizado
    subscript(index: Int) -> String {
        return names[index]
    }
}

// Las estructuras tienen un inicializador designado autogenerado (implícitamente)
let namesTable = NamesTable(names: ["Me", "Them"])
let name = namesTable[1]
print("Name is \(name)") // Name is Them

//
// MARK: Clases
//

// Las clases, las estructuras y sus miembros tienen tres niveles de control de acceso
// Éstos son: internal (predeterminado), public, private

public class Shape {
    public func getArea() -> Int {
        return 0;
    }
}

// Todos los métodos y las propiedades de una clase son public (públicas)
// Si solo necesitas almacenar datos en un objecto estructurado,
// debes de utilizar `struct`

internal class Rect: Shape {
    var sideLength: Int = 1

    // Getter y setter personalizado
    private var perimeter: Int {
        get {
            return 4 * sideLength
        }
        set {
            // `newValue` es una variable implícita disponible para los setters
            sideLength = newValue / 4
        }
    }

    // Lazily loading (inicialización bajo demanda) a una propiedad
    // subShape queda como nil (sin inicializar) hasta que getter es llamado
    lazy var subShape = Rect(sideLength: 4)

    // Si no necesitas un getter y setter personalizado
    // pero aún quieres ejecutar código antes y después de hacer get o set
    // a una propiedad, puedes utilizar `willSet` y `didSet`    
    var identifier: String = "defaultID" {
        // El argumento `willSet` será el nombre de variable para el nuevo valor
        willSet(someIdentifier) {
            print(someIdentifier)
        }
    }

    init(sideLength: Int) {
        self.sideLength = sideLength
        // Siempre poner super.init de último al momento de inicializar propiedades
        // personalizadas
        super.init()
    }

    func shrink() {
        if sideLength > 0 {
            sideLength -= 1
        }
    }

    override func getArea() -> Int {
        return sideLength * sideLength
    }
}

// Una clase simple `Square` que extiende de `Rect`
class Square: Rect {
    convenience init() {
        self.init(sideLength: 5)
    }
}

var mySquare = Square()
print(mySquare.getArea()) // 25
mySquare.shrink()
print(mySquare.sideLength) // 4

// Conversión de tipo de instancia
let aShape = mySquare as Shape

// Comparar instancias, no es igual a == que compara objetos (equal to)
if mySquare === mySquare {
    print("Yep, it's mySquare")
}

// Inicialización (init) opcional
class Circle: Shape {
    var radius: Int
    override func getArea() -> Int {
        return 3 * radius * radius
    }

    // Un signo de interrogación como sufijo después de `init` es un init opcional
    // que puede devolver nil    
    init?(radius: Int) {
        self.radius = radius
        super.init()

        if radius <= 0 {
            return nil
        }
    }
}

var myCircle = Circle(radius: 1)
print(myCircle?.getArea())    // Optional(3)
print(myCircle!.getArea())    // 3
var myEmptyCircle = Circle(radius: -1)
print(myEmptyCircle?.getArea())    // "nil"
if let circle = myEmptyCircle {
    // no será ejecutado debido a que myEmptyCircle es nil
    print("circle is not nil")
}


//
// MARK: Enums
//


// Los enums pueden ser opcionalmente de un tipo específico o de su propio tipo
// Al igual que las clases, pueden contener métodos

enum Suit {
    case spades, hearts, diamonds, clubs
    func getIcon() -> String {
        switch self {
        case .spades: return "♤"
        case .hearts: return "♡"
        case .diamonds: return "♢"
        case .clubs: return "♧"
        }
    }
}

// Los valores de enum permite la sintaxis corta, sin necesidad de poner
// el tipo del enum cuando la variable es declarada de manera explícita
var suitValue: Suit = .hearts

// Enums de tipo no-entero requiere asignaciones de valores crudas directas
enum BookName: String {
    case john = "John"
    case luke = "Luke"
}
print("Name: \(BookName.john.rawValue)")

// Enum con valores asociados
enum Furniture {
    // Asociación con Int
    case desk(height: Int)
    // Asociación con String e Int
    case chair(String, Int)

    func description() -> String {
        switch self {
        case .desk(let height):
            return "Desk with \(height) cm"
        case .chair(let brand, let height):
            return "Chair of \(brand) with \(height) cm"
        }
    }
}

var desk: Furniture = .desk(height: 80)
print(desk.description())     // "Desk with 80 cm"
var chair = Furniture.chair("Foo", 40)
print(chair.description())    // "Chair of Foo with 40 cm"


//
// MARK: Protocolos
//

// `protocol` puede requerir que los tipos tengan propiedades
// de instancia específicas, métodos de instancia, métodos de tipo,
// operadores, y subscripts


protocol ShapeGenerator {
    var enabled: Bool { get set }
    func buildShape() -> Shape
}

// Protocolos declarados con @objc permiten funciones opcionales,
// que te permite evaluar conformidad
@objc protocol TransformShape {
    optional func reshaped()
    optional func canReshape() -> Bool
}

class MyShape: Rect {
    var delegate: TransformShape?

    func grow() {
        sideLength += 2

        // Pon un signo de interrogación después de la propiedad opcional,
        // método, o subscript para ignorar un valor nil y devolver nil
        // en lugar de  tirar un error de tiempo de ejecución
        // ("optional chaining")        
        if let allow = self.delegate?.canReshape?() {
            // test for delegate then for method
            self.delegate?.reshaped?()
        }
    }
}


//
// MARK: Otros
//

// `extension`: Agrega funcionalidades a tipos existentes

// Square ahora se "conforma" al protocolo `Printable`
extension Square: Printable {
    var description: String {
        return "Area: \(self.getArea()) - ID: \(self.identifier)"
    }
}

print("Square: \(mySquare)")

// También puedes hacer extend a tipos prefabricados (built-in)
extension Int {
    var customProperty: String {
        return "This is \(self)"
    }

    func multiplyBy(num: Int) -> Int {
        return num * self
    }
}

print(7.customProperty) // "This is 7"
print(14.multiplyBy(3)) // 42

// Generics: Similar Java y C#. Utiliza la palabra clave `where` para
// especificar los requerimientos de los genéricos.

func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
    for (index, value) in enumerate(array) {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
let foundAtIndex = findIndex([1, 2, 3, 4], 3)
print(foundAtIndex == 2) // true

// Operadores:
// Operadores personalizados puede empezar con los siguientes caracteres:
//      / = - + * % < > ! & | ^ . ~
// o
// Caracteres unicode: math, symbol, arrow, dingbat, y line/box.
prefix operator !!! {}

// Un operador prefix que triplica la longitud del lado cuando es utilizado
prefix func !!! (inout shape: Square) -> Square {
    shape.sideLength *= 3
    return shape
}

// Valor actual
print(mySquare.sideLength) // 4

// Cambiar la longitud del lado utilizando el operador !!!,
// incrementa el tamaño por 3
!!!mySquare
print(mySquare.sideLength) // 12

¿Tienes una sugerencia o rectificación? Abre un issue en el repositorio de Github, o haz un pull request tu mismo

Originalmente contribuido por Grant Timmerman, y actualizado por 2 colaborador(es).