Un recorrido rápido

Adaptado del A Swift Tour original en Swift.org con modificaciones. El contenido original fue escrito por Apple Inc. Con licencia Creative Commons Attribution 4.0 International (CC BY 4.0) .
Ver en TensorFlow.org Ver código fuente en GitHub

La tradición sugiere que el primer programa en un nuevo idioma debería imprimir las palabras "¡Hola, mundo!" en la pantalla. En Swift, esto se puede hacer en una sola línea:

print("Hello, world!")
Hello, world!

Si ha escrito código en C u Objective-C, esta sintaxis le resultará familiar; en Swift, esta línea de código es un programa completo. No necesita importar una biblioteca separada para funciones como entrada/salida o manejo de cadenas. El código escrito en el ámbito global se utiliza como punto de entrada para el programa, por lo que no necesita una función main() . Tampoco es necesario escribir punto y coma al final de cada declaración.

Este recorrido le brinda suficiente información para comenzar a escribir código en Swift y le muestra cómo realizar una variedad de tareas de programación. No se preocupe si no comprende algo: todo lo que se presenta en este recorrido se explica en detalle en el resto de este libro.

Valores simples

Use let para hacer una constante y var para hacer una variable. No es necesario conocer el valor de una constante en el momento de la compilación, pero debes asignarle un valor exactamente una vez. Esto significa que puede usar constantes para nombrar un valor que determina una vez pero que usa en muchos lugares.

var myVariable = 42
myVariable = 50
let myConstant = 42

Una constante o variable debe tener el mismo tipo que el valor que le deseas asignar. Sin embargo, no siempre es necesario escribir el tipo explícitamente. Proporcionar un valor al crear una constante o variable permite al compilador inferir su tipo. En el ejemplo anterior, el compilador infiere que myVariable es un número entero porque su valor inicial es un número entero.

Si el valor inicial no proporciona suficiente información (o si no hay un valor inicial), especifique el tipo escribiéndolo después de la variable, separado por dos puntos. Nota: usar Double en lugar de Float para números de punto flotante le brinda más precisión y es el tipo de punto flotante predeterminado en Swift.

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
// Experiment:
// Create a constant with an explicit type of `Float` and a value of 4.

Los valores nunca se convierten implícitamente a otro tipo. Si necesita convertir un valor a un tipo diferente, cree explícitamente una instancia del tipo deseado.

let label = "The width is "
let width = 94
print(label + String(width))
The width is 94

// Experiment:
// Try removing the conversion to `String` from the last line. What error do you get?

Hay una forma aún más sencilla de incluir valores en cadenas: escriba el valor entre paréntesis y escriba una barra invertida (``) antes de los paréntesis. Por ejemplo:

let apples = 3
print("I have \(apples) apples.")
I have 3 apples.

let oranges = 5
print("I have \(apples + oranges) pieces of fruit.")
I have 8 pieces of fruit.

// Experiment:
// Use `\()` to include a floating-point calculation in a string and to include someone's name in a
// greeting.

Utilice tres comillas dobles ( """ ) para cadenas que ocupen varias líneas. La sangría al inicio de cada línea entre comillas se elimina, siempre que coincida con la sangría de las comillas de cierre. Por ejemplo:

let quotation = """
    Even though there's whitespace to the left,
    the actual lines aren't indented.
        Except for this line.
    Double quotes (") can appear without being escaped.

    I still have \(apples + oranges) pieces of fruit.
    """
print(quotation)
Even though there's whitespace to the left,
the actual lines aren't indented.
    Except for this line.
Double quotes (") can appear without being escaped.

I still have 8 pieces of fruit.

Cree matrices y diccionarios usando corchetes ( [] ) y acceda a sus elementos escribiendo el índice o la clave entre corchetes. Se permite una coma después del último elemento.

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
print(occupations)
["Jayne": "Public Relations", "Kaylee": "Mechanic", "Malcolm": "Captain"]

Las matrices crecen automáticamente a medida que agrega elementos.

shoppingList.append("blue paint")
print(shoppingList)
["catfish", "bottle of water", "tulips", "blue paint", "blue paint"]

Para crear una matriz o un diccionario vacío, utilice la sintaxis del inicializador.

let emptyArray = [String]()
let emptyDictionary = [String: Float]()

Si se puede inferir información de tipo, puede escribir una matriz vacía como [] y un diccionario vacío como [:] ; por ejemplo, cuando establece un nuevo valor para una variable o pasa un argumento a una función.

shoppingList = []
occupations = [:]

Flujo de control

Utilice if y switch para crear condicionales y utilice for - in , for , while y repeat - while para crear bucles. Los paréntesis alrededor de la condición o variable de bucle son opcionales. Se requieren aparatos ortopédicos alrededor del cuerpo.

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)
11

En una declaración if , el condicional debe ser una expresión booleana; esto significa que código como if score { ... } es un error, no una comparación implícita con cero.

Puede utilizar if y let juntos para trabajar con valores que puedan faltar. Estos valores se representan como opcionales. Un valor opcional contiene un valor o contiene nil para indicar que falta un valor. Escriba un signo de interrogación ( ? ) después del tipo de valor para marcar el valor como opcional.

var optionalString: String? = "Hello"
print(optionalString == nil)
false

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
print(greeting)
Hello, John Appleseed

// Experiment:
// Change `optionalName` to `nil`. What greeting do you get?
// Add an `else` clause that sets a different greeting if `optionalName` is `nil`.

Si el valor opcional es nil , el condicional es false y se omite el código entre llaves. De lo contrario, el valor opcional se desenvuelve y se asigna a la constante después let , lo que hace que el valor desenvuelto esté disponible dentro del bloque de código.

Otra forma de manejar valores opcionales es proporcionar un valor predeterminado usando el comando ?? operador. Si falta el valor opcional, se utiliza el valor predeterminado.

let nickName: String? = nil
let fullName: String = "John Appleseed"
print("Hi \(nickName ?? fullName)")
Hi John Appleseed

Los conmutadores admiten cualquier tipo de datos y una amplia variedad de operaciones de comparación; no se limitan a números enteros y pruebas de igualdad.

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}
Is it a spicy red pepper?

// Experiment:
// Try removing the default case. What error do you get?

Observe cómo se puede usar let en un patrón para asignar el valor que coincide con esa parte de un patrón a una constante.

Después de ejecutar el código dentro del caso de cambio que coincidió, el programa sale de la declaración de cambio. La ejecución no continúa con el siguiente caso, por lo que no es necesario salir explícitamente del cambio al final del código de cada caso.

Se utiliza for - in para iterar sobre elementos de un diccionario proporcionando un par de nombres para usar en cada par clave-valor. Los diccionarios son una colección desordenada, por lo que sus claves y valores se repiten en un orden arbitrario.

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
25

// Experiment:
// Add another variable to keep track of which kind of number was the largest, as well as what that
// largest number was.

Utilice while para repetir un bloque de código hasta que cambie una condición. En cambio, la condición de un bucle puede estar al final, asegurando que el bucle se ejecute al menos una vez.

var n = 2
while n < 100 {
    n = n * 2
}

print(n)
128

var m = 2
repeat {
    m = m * 2
} while m < 100

print(m)
128

Puede mantener un índice en un bucle, ya sea usando ..< para crear un rango de índices o escribiendo una inicialización, condición e incremento explícitos. Estos dos bucles hacen lo mismo:

var total = 0
for i in 0..<4 {
    total += i
}

print(total)
6

Utilice ..< para crear un rango que omita su valor superior y utilice ... para crear un rango que incluya ambos valores.

Funciones y Cierres

Utilice func para declarar una función. Llame a una función siguiendo su nombre con una lista de argumentos entre paréntesis. Utilice -> para separar los nombres y tipos de parámetros del tipo de retorno de la función.

func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)."
}
print(greet(name: "Bob", day: "Tuesday"))
Hello Bob, today is Tuesday.

// Experiment:
// Remove the `day` parameter. Add a parameter to include today’s lunch special in the greeting.

De forma predeterminada, las funciones utilizan los nombres de sus parámetros como etiquetas para sus argumentos. Escriba una etiqueta de argumento personalizada antes del nombre del parámetro o escriba _ para no utilizar ninguna etiqueta de argumento.

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
print(greet("John", on: "Wednesday"))
Hello John, today is Wednesday.

Utilice una tupla para crear un valor compuesto; por ejemplo, para devolver varios valores de una función. Se puede hacer referencia a los elementos de una tupla por su nombre o por su número.

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
120
120

Las funciones se pueden anidar. Las funciones anidadas tienen acceso a variables que fueron declaradas en la función externa. Puede utilizar funciones anidadas para organizar el código en una función que sea larga o compleja.

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
print(returnFifteen())
15

Las funciones son un tipo de primera clase. Esto significa que una función puede devolver otra función como valor.

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
print(increment(7))
8

Una función puede tomar otra función como uno de sus argumentos.

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
true

Las funciones son en realidad un caso especial de cierres: bloques de código que se pueden llamar más tarde. El código de un cierre tiene acceso a elementos como variables y funciones que estaban disponibles en el ámbito donde se creó el cierre, incluso si el cierre se encuentra en un ámbito diferente cuando se ejecuta; ya vio un ejemplo de esto con funciones anidadas. Puede escribir un cierre sin nombre rodeando el código entre llaves ( {} ). in para separar los argumentos y el tipo de retorno del cuerpo.

let mappedNumbers = numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})
print(mappedNumbers)
[60, 57, 21, 36]

// Experiment:
// Rewrite the closure to return zero for all odd numbers.

Tiene varias opciones para escribir cierres de manera más concisa. Cuando ya se conoce el tipo de cierre, como la devolución de llamada de un delegado, puede omitir el tipo de sus parámetros, su tipo de retorno o ambos. Los cierres de declaraciones únicas devuelven implícitamente el valor de su única declaración.

let mappedNumbers2 = numbers.map({ number in 3 * number })
print(mappedNumbers2)
[60, 57, 21, 36]

Puede hacer referencia a los parámetros por número en lugar de por nombre; este enfoque es especialmente útil en cierres muy cortos. Un cierre pasado como último argumento de una función puede aparecer inmediatamente después del paréntesis. Cuando un cierre es el único argumento de una función, puedes omitir los paréntesis por completo.

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
[20, 19, 12, 7]

Objetos y clases

Utilice class seguido del nombre de la clase para crear una clase. Una declaración de propiedad en una clase se escribe de la misma manera que una declaración de constante o variable, excepto que está en el contexto de una clase. Asimismo, las declaraciones de métodos y funciones se escriben de la misma manera.

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
// Experiment:
// Add a constant property with `let`, and add another method that takes an argument.

Cree una instancia de una clase poniendo paréntesis después del nombre de la clase. Utilice la sintaxis de puntos para acceder a las propiedades y métodos de la instancia.

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

A esta versión de la clase Shape le falta algo importante: un inicializador para configurar la clase cuando se crea una instancia. Utilice init para crear uno.

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Observe cómo se usa self para distinguir la propiedad de name del argumento de name del inicializador. Los argumentos del inicializador se pasan como una llamada a función cuando se crea una instancia de la clase. Cada propiedad necesita un valor asignado, ya sea en su declaración (como con numberOfSides ) o en el inicializador (como con name ).

Utilice deinit para crear un desinicializador si necesita realizar alguna limpieza antes de que se desasigne el objeto.

Las subclases incluyen su nombre de superclase después del nombre de su clase, separados por dos puntos. No es necesario que las clases creen subclases en ninguna clase raíz estándar, por lo que puede incluir u omitir una superclase según sea necesario.

Los métodos de una subclase que anulan la implementación de la superclase están marcados con override : el compilador detecta como un error anular un método por accidente, sin override . El compilador también detecta métodos con override que en realidad no anulan ningún método en la superclase.

class Square: NamedShape {
    var sideLength: Double

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }

    func area() -> Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
print(test.area())
print(test.simpleDescription())
27.040000000000003
A square with sides of length 5.2.

// Experiment:
// - Make another subclass of `NamedShape` called `Circle` that takes a radius and a name as
//   arguments to its initializer.
// - Implement an `area()` and a `simpleDescription()` method on the `Circle` class.

Además de las propiedades simples que se almacenan, las propiedades pueden tener un captador y un definidor.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }

    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
9.3
3.3000000000000003

En el definidor de perimeter , el nuevo valor tiene el nombre implícito newValue . Puede proporcionar un nombre explícito entre paréntesis después de set .

Observe que el inicializador de la clase EquilateralTriangle tiene tres pasos diferentes:

  1. Establecer el valor de las propiedades que declara la subclase.

  2. Llamando al inicializador de la superclase.

  3. Cambiar el valor de las propiedades definidas por la superclase. En este punto también se puede realizar cualquier trabajo de configuración adicional que utilice métodos, captadores o definidores.

Si no necesita calcular la propiedad pero aún necesita proporcionar el código que se ejecuta antes y después de establecer un nuevo valor, use willSet y didSet . El código que proporciona se ejecuta cada vez que el valor cambia fuera de un inicializador. Por ejemplo, la siguiente clase garantiza que la longitud del lado de su triángulo sea siempre la misma que la longitud del lado de su cuadrado.

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
10.0
10.0
50.0

Cuando trabaja con valores opcionales, puede escribir ? antes de operaciones como métodos, propiedades y subíndices. Si el valor antes de ? es nil , todo después de ? se ignora y el valor de toda la expresión es nil . De lo contrario, el valor opcional se desenvuelve y todo lo que sigue a ? actúa sobre el valor desenvuelto. En ambos casos, el valor de la expresión completa es un valor opcional.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
print(optionalSquare?.sideLength)
Optional(2.5)

Enumeraciones y estructuras

Utilice enum para crear una enumeración. Al igual que las clases y todos los demás tipos con nombre, las enumeraciones pueden tener métodos asociados.

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king

    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
print(ace)
let aceRawValue = ace.rawValue
print(aceRawValue)
ace
1

// Experiment:
// Write a function that compares two `Rank` values by comparing their raw values.

De forma predeterminada, Swift asigna los valores sin procesar comenzando en cero e incrementando en uno cada vez, pero puede cambiar este comportamiento especificando valores explícitamente. En el ejemplo anterior, a Ace se le asigna explícitamente un valor bruto de 1 y el resto de los valores brutos se asignan en orden. También puede utilizar cadenas o números de punto flotante como tipo sin formato de una enumeración. Utilice la propiedad rawValue para acceder al valor sin formato de un caso de enumeración.

Utilice el inicializador init?(rawValue:) para crear una instancia de una enumeración a partir de un valor sin formato. Devuelve el caso de enumeración que coincide con el valor sin formato o nil si no hay ningún Rank coincidente.

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

Los valores de caso de una enumeración son valores reales, no simplemente otra forma de escribir sus valores brutos. De hecho, en los casos en los que no haya un valor bruto significativo, no es necesario proporcionarlo.

enum Suit {
    case spades, hearts, diamonds, clubs

    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
// Experiment:
// Add a `color()` method to `Suit` that returns "black" for spades and clubs, and returns "red" for
// hearts and diamonds.

Observe las dos formas en que se hace referencia al caso de enumeración Hearts anteriormente: cuando se asigna un valor a la constante hearts , se hace referencia al caso de enumeración Suit.Hearts por su nombre completo porque la constante no tiene un tipo explícito especificado. Dentro del interruptor, el caso de enumeración se denomina con la forma abreviada .Hearts porque ya se sabe que el valor de self es un palo. Puede utilizar la forma abreviada siempre que ya se conozca el tipo de valor.

Si una enumeración tiene valores sin procesar, esos valores se determinan como parte de la declaración, lo que significa que cada instancia de un caso de enumeración particular siempre tiene el mismo valor sin procesar. Otra opción para los casos de enumeración es tener valores asociados con el caso; estos valores se determinan cuando crea la instancia y pueden ser diferentes para cada instancia de un caso de enumeración. Puede pensar que los valores asociados se comportan como propiedades almacenadas de la instancia del caso de enumeración.

Por ejemplo, considere el caso de solicitar las horas de salida y puesta del sol a un servidor. El servidor responde con la información solicitada o responde con una descripción de lo que salió mal.

enum ServerResponse {
    case result(String, String)
    case failure(String)
}

let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")

switch success {
case let .result(sunrise, sunset):
    print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
    print("Failure...  \(message)")
}
Sunrise is at 6:00 am and sunset is at 8:09 pm.

// Experiment:
// Add a third case to `ServerResponse` and to the switch.

Observe cómo las horas de salida y puesta del sol se extraen del valor ServerResponse como parte de hacer coincidir el valor con los casos de cambio.

Utilice struct para crear una estructura. Las estructuras admiten muchos de los mismos comportamientos que las clases, incluidos métodos e inicializadores. Una de las diferencias más importantes entre estructuras y clases es que las estructuras siempre se copian cuando se pasan en el código, pero las clases se pasan por referencia.

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
// Experiment:
// Write a function that returns an array containing a full deck of cards, with one card of each
// combination of rank and suit.

Protocolos y Extensiones

Utilice protocol para declarar un protocolo.

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

Todas las clases, enumeraciones y estructuras pueden adoptar protocolos.

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
print(b.adjust())
print(b.simpleDescription)
()
A simple structure (adjusted)

// Experiment:
// Add another requirement to `ExampleProtocol`.
// What changes do you need to make to `SimpleClass` and `SimpleStructure` so that they still
// conform to the protocol?

Observe el uso de la palabra clave mutating en la declaración de SimpleStructure para marcar un método que modifica la estructura. La declaración de SimpleClass no necesita que ninguno de sus métodos esté marcado como mutante porque los métodos de una clase siempre pueden modificar la clase.

Utilice extension para agregar funcionalidad a un tipo existente, como nuevos métodos y propiedades calculadas. Puede usar una extensión para agregar conformidad de protocolo a un tipo declarado en otro lugar, o incluso a un tipo que importó de una biblioteca o marco.

extension Int: ExampleProtocol {
    public var simpleDescription: String {
        return "The number \(self)"
    }
    public mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)
The number 7

// Experiment:
// Write an extension for the `Double` type that adds an `absoluteValue` property.

Puede utilizar un nombre de protocolo como cualquier otro tipo con nombre; por ejemplo, para crear una colección de objetos que tienen diferentes tipos pero que todos se ajustan a un único protocolo. Cuando trabaja con valores cuyo tipo es un tipo de protocolo, los métodos fuera de la definición del protocolo no están disponibles.

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
A very simple class.  Now 100% adjusted.

// Uncomment to see the error.
// protocolValue.anotherProperty

Aunque la variable protocolValue tiene un tipo de tiempo de ejecución de SimpleClass , el compilador la trata como el tipo dado de ExampleProtocol . Esto significa que no puede acceder accidentalmente a métodos o propiedades que implementa la clase además de su conformidad con el protocolo.

Manejo de errores

Representas errores utilizando cualquier tipo que adopte el protocolo Error .

enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

Utilice throw para generar un error y throws para marcar una función que puede generar un error. Si arroja un error en una función, la función regresa inmediatamente y el código que llamó a la función maneja el error.

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

Hay varias formas de manejar los errores. Una forma es utilizar do-catch . Dentro del bloque do , marca el código que puede generar un error escribiendo try delante de él. Dentro del bloque catch , al error se le asigna automáticamente el nombre error a menos que le dé un nombre diferente.

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
Job sent

// Experiment:
// Change the printer name to `"Never Has Toner"`, so that the `send(job:toPrinter:)` function
// throws an error.

Puede proporcionar varios bloques catch que manejen errores específicos. Escribe un patrón después de catch tal como lo hace después del case en un interruptor.

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
Job sent

// Experiment:
// Add code to throw an error inside the `do` block.
// What kind of error do you need to throw so that the error is handled by the first `catch` block?
// What about the second and third blocks?

¿Otra forma de manejar los errores es utilizar try? para convertir el resultado en opcional. Si la función arroja un error, el error específico se descarta y el resultado es nil . De lo contrario, el resultado es opcional y contiene el valor que devolvió la función.

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

Utilice defer para escribir un bloque de código que se ejecuta después de todo el resto del código de la función, justo antes de que la función regrese. El código se ejecuta independientemente de si la función arroja un error. Puede utilizar defer para escribir el código de configuración y limpieza uno al lado del otro, aunque deban ejecutarse en momentos diferentes.

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }

    let result = fridgeContent.contains(food)
    return result
}
print(fridgeContains("banana"))
print(fridgeIsOpen)
false
false

Genéricos

Escriba un nombre entre corchetes angulares para crear una función o tipo genérico.

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
print(makeArray(repeating: "knock", numberOfTimes: 4))
["knock", "knock", "knock", "knock"]

Puede crear formas genéricas de funciones y métodos, así como clases, enumeraciones y estructuras.

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
print(possibleInteger)
some(100)

Utilice where después del nombre del tipo para especificar una lista de requisitos; por ejemplo, para exigir que el tipo implemente un protocolo, para exigir que dos tipos sean iguales o para exigir que una clase tenga una superclase particular.

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
print(anyCommonElements([1, 2, 3], [3]))
true

Escribir <T: Equatable> es lo mismo que escribir <T> ... where T: Equatable .