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 de Creative Commons Attribution 4.0 International (CC BY 4.0) License .
Ver en TensorFlow.org Ver fuente en GitHub

La tradición sugiere que el primer programa en un nuevo idioma debe 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 usa como punto de entrada para el programa, por lo que no necesita una función main() . Tampoco necesita 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 al mostrarle cómo realizar una variedad de tareas de programación. No se preocupe si no entiende algo: todo lo presentado 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 debe 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 desea asignarle. Sin embargo, no siempre tiene que escribir el tipo explícitamente. Proporcionar un valor cuando crea una constante o variable le 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 valor inicial), especifique el tipo escribiéndolo después de la variable, separado por dos puntos. Nota: el uso de 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. Se elimina la sangría al comienzo de cada línea entre comillas, 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 arreglos 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 diccionario vacío, use 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

Usa if y switch para hacer condicionales, y usa for - in , for , while y repeat - while para hacer bucles. Los paréntesis alrededor de la variable de condición o 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 un código como if score { ... } es un error, no una comparación implícita con cero.

Puede usar if y let juntos para trabajar con valores que pueden 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 de 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 ?? operador. Si falta el valor opcional, se utiliza el valor predeterminado en su lugar.

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 coincidió con esa parte de un patrón a una constante.

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

Se usa for - in para iterar sobre elementos en un diccionario al proporcionar un par de nombres para usar para cada par clave-valor. Los diccionarios son una colección desordenada, por lo que sus claves y valores se iteran 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.

Use while para repetir un bloque de código hasta que cambie una condición. En cambio, la condición de un ciclo puede estar al final, lo que garantiza que el ciclo 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, una condición y un incremento explícitos. Estos dos bucles hacen lo mismo:

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

print(total)
6

Use ..< para hacer un rango que omita su valor superior y use ... para hacer un rango que incluya ambos valores.

Funciones y Cierres

Use func para declarar una función. Llame a una función siguiendo su nombre con una lista de argumentos entre paréntesis. Use -> 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 usan sus nombres de parámetros como etiquetas para sus argumentos. Escriba una etiqueta de argumento personalizada antes del nombre del parámetro, o escriba _ para no usar 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 nombre o por 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 las variables que se declararon en la función externa. Puede usar funciones anidadas para organizar el código en una función 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 su 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 en un cierre tiene acceso a cosas como variables y funciones que estaban disponibles en el ámbito donde se creó el cierre, incluso si el cierre está 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 con llaves ( {} ). Use 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 forma más concisa. Cuando ya se conoce el tipo de cierre, como la devolución de llamada para un delegado, puede omitir el tipo de sus parámetros, su tipo de devolución o ambos. Los cierres de declaraciones individuales 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 de los paréntesis. Cuando un cierre es el único argumento de una función, puede omitir los paréntesis por completo.

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

Objetos y Clases

Use 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. Del mismo modo, 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 del name del argumento del name del inicializador. Los argumentos al inicializador se pasan como una llamada de función cuando 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 ).

Use deinit para crear un desinicializador si necesita realizar alguna limpieza antes de desasignar el objeto.

Las subclases incluyen su nombre de superclase después de su nombre de clase, separados por dos puntos. No hay ningún requisito para que las clases sean subclases de cualquier clase raíz estándar, por lo que puede incluir u omitir una superclase según sea necesario.

Los métodos en una subclase que anulan la implementación de la superclase se marcan 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 getter y un setter.

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 setter para 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. Llamar al inicializador de la superclase.

  3. Cambiar el valor de las propiedades definidas por la superclase. Cualquier trabajo de configuración adicional que utilice métodos, getters o setters también se puede realizar en este punto.

Si no necesita calcular la propiedad pero aún necesita proporcionar un 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 asegura 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 trabaje 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 se encuentra después de ? actúa sobre el valor no envuelto. 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

Use 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 que comienzan en cero y se incrementan en uno cada vez, pero puede cambiar este comportamiento especificando valores explícitamente. En el ejemplo anterior, Ace recibe 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 bruto de un caso de enumeración.

Utilice el 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 bruto o nil si no hay un Rank coincidente.

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

Los valores de caso de una enumeración son valores reales, no solo otra forma de escribir sus valores brutos. De hecho, en los casos en que no haya un valor sin procesar significativo, no es necesario que proporcione uno.

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 Hearts de la enumeración anterior: Al asignar 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 mediante la forma abreviada .Hearts porque ya se sabe que el valor de self es un palo. Puede utilizar la forma abreviada siempre que ya 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 desde 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 se extraen las horas de salida y puesta del sol del valor de ServerResponse como parte de la comparación del valor con los casos de cambio.

Usa 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 las estructuras y las clases es que las estructuras siempre se copian cuando se pasan por 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

Use protocol para declarar un protocolo.

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

Las clases, las enumeraciones y las 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.

Use la 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 que se declara 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 usar 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 se ajustan a un solo 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

Los errores se representan utilizando cualquier tipo que adopte el protocolo Error .

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

Use throw para arrojar un error y throws para marcar una función que puede arrojar 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 usar do-catch . Dentro del bloque do , marca el código que puede arrojar un error escribiendo try delante de él. Dentro del bloque catch , el error recibe automáticamente el nombre de 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 múltiples bloques catch que manejen errores específicos. Escribes un patrón después de catch tal como lo haces después de un 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 usar try? para convertir el resultado en un 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 un opcional que 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")

Use defer para escribir un bloque de código que se ejecuta después de todo el otro código en 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 usar 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

Escribe un nombre entre paréntesis angulares para hacer una función o un 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)

Use where después del nombre del tipo para especificar una lista de requisitos, por ejemplo, para requerir que el tipo implemente un protocolo, para requerir que dos tipos sean iguales o para requerir 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 .