Um passeio rápido

Adaptado do A Swift Tour original em Swift.org com modificações. O conteúdo original foi de autoria da Apple Inc. Licenciado sob a licença Creative Commons Atribuição 4.0 Internacional (CC BY 4.0) .
Ver em TensorFlow.org Execute no Google Colab Ver fonte no GitHub

A tradição sugere que o primeiro programa em uma nova linguagem imprima as palavras "Olá, mundo!" na tela. No Swift, isso pode ser feito em uma única linha:

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

Se você escreveu código em C ou Objective-C, esta sintaxe lhe parece familiar – em Swift, esta linha de código é um programa completo. Você não precisa importar uma biblioteca separada para funcionalidades como entrada/saída ou manipulação de strings. O código escrito em escopo global é usado como ponto de entrada para o programa, portanto você não precisa de uma função main() . Você também não precisa escrever ponto e vírgula no final de cada instrução.

Este tour fornece informações suficientes para começar a escrever código em Swift, mostrando como realizar uma variedade de tarefas de programação. Não se preocupe se não entender alguma coisa – tudo o que é apresentado neste tour é explicado em detalhes no restante deste livro.

Valores Simples

Use let para criar uma constante e var para criar uma variável. O valor de uma constante não precisa ser conhecido em tempo de compilação, mas você deve atribuir um valor a ela exatamente uma vez. Isso significa que você pode usar constantes para nomear um valor que você determina uma vez, mas usa em muitos lugares.

var myVariable = 42
myVariable = 50
let myConstant = 42

Uma constante ou variável deve ter o mesmo tipo do valor que você deseja atribuir a ela. No entanto, nem sempre é necessário escrever o tipo explicitamente. Fornecer um valor ao criar uma constante ou variável permite que o compilador infira seu tipo. No exemplo acima, o compilador infere que myVariable é um número inteiro porque seu valor inicial é um número inteiro.

Se o valor inicial não fornecer informações suficientes (ou se não houver valor inicial), especifique o tipo escrevendo-o após a variável, separado por dois pontos. Nota: usar Double em vez de Float para números de ponto flutuante oferece mais precisão e é o tipo de ponto flutuante padrão no 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.

Os valores nunca são convertidos implicitamente para outro tipo. Se você precisar converter um valor para um tipo diferente, crie explicitamente uma instância do tipo desejado.

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?

Existe uma maneira ainda mais simples de incluir valores em strings: escreva o valor entre parênteses e uma barra invertida (``) antes dos parênteses. Por exemplo:

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.

Use três aspas duplas ( """ ) para strings que ocupam várias linhas. O recuo no início de cada linha entre aspas é removido, desde que corresponda ao recuo das aspas de fechamento. Por exemplo:

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.

Crie arrays e dicionários usando colchetes ( [] ) e acesse seus elementos escrevendo o índice ou chave entre colchetes. Uma vírgula é permitida após o ú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"]

As matrizes crescem automaticamente à medida que você adiciona elementos.

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

Para criar um array ou dicionário vazio, use a sintaxe do inicializador.

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

Se as informações de tipo puderem ser inferidas, você poderá escrever um array vazio como [] e um dicionário vazio como [:] — por exemplo, quando você define um novo valor para uma variável ou passa um argumento para uma função.

shoppingList = []
occupations = [:]

Fluxo de controle

Use if e switch para fazer condicionais e use for - in , for , while e repeat - while para fazer loops. Parênteses em torno da condição ou variável de loop são opcionais. São necessários aparelhos ao redor do corpo.

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

Em uma instrução if , a condicional deve ser uma expressão booleana – isso significa que um código como if score { ... } é um erro, não uma comparação implícita com zero.

Você pode usar if e let juntos para trabalhar com valores que possam estar faltando. Esses valores são representados como opcionais. Um valor opcional contém um valor ou contém nil para indicar que um valor está faltando. Escreva um ponto de interrogação ( ? ) após o tipo de valor para marcá-lo 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`.

Se o valor opcional for nil , a condicional será false e o código entre colchetes será ignorado. Caso contrário, o valor opcional é desembrulhado e atribuído à constante após let , o que disponibiliza o valor desembrulhado dentro do bloco de código.

Outra maneira de lidar com valores opcionais é fornecer um valor padrão usando o ?? operador. Se o valor opcional estiver faltando, o valor padrão será usado.

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

Os switches suportam qualquer tipo de dados e uma ampla variedade de operações de comparação — eles não estão limitados a números inteiros e testes de igualdade.

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 como let pode ser usado em um padrão para atribuir o valor que corresponde àquela parte de um padrão a uma constante.

Depois de executar o código dentro do switch case correspondente, o programa sai da instrução switch. A execução não continua para o próximo caso, portanto não há necessidade de interromper explicitamente a opção no final do código de cada caso.

Você usa for - in para iterar itens em um dicionário, fornecendo um par de nomes para usar para cada par de valores-chave. Os dicionários são uma coleção não ordenada, portanto suas chaves e valores são iterados em ordem arbitrária.

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 um bloco de código até que uma condição mude. A condição de um loop pode estar no final, garantindo que o loop seja executado pelo menos uma 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

Você pode manter um índice em um loop - usando ..< para criar um intervalo de índices ou escrevendo uma inicialização, condição e incremento explícitos. Esses dois loops fazem a mesma coisa:

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

print(total)
6

Use ..< para criar um intervalo que omite seu valor superior e use ... para criar um intervalo que inclua ambos os valores.

Funções e Fechamentos

Use func para declarar uma função. Chame uma função seguindo seu nome com uma lista de argumentos entre parênteses. Use -> para separar os nomes e tipos dos parâmetros do tipo de retorno da função.

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.

Por padrão, as funções usam seus nomes de parâmetros como rótulos para seus argumentos. Escreva um rótulo de argumento personalizado antes do nome do parâmetro ou escreva _ para não usar nenhum rótulo 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.

Use uma tupla para criar um valor composto — por exemplo, para retornar vários valores de uma função. Os elementos de uma tupla podem ser referidos por nome ou 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

As funções podem ser aninhadas. Funções aninhadas têm acesso a variáveis ​​que foram declaradas na função externa. Você pode usar funções aninhadas para organizar o código em uma função longa ou complexa.

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

Funções são um tipo de primeira classe. Isso significa que uma função pode retornar outra função como valor.

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

Uma função pode receber outra função como um de seus 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

As funções são, na verdade, um caso especial de encerramentos: blocos de código que podem ser chamados posteriormente. O código em um encerramento tem acesso a coisas como variáveis ​​e funções que estavam disponíveis no escopo onde o encerramento foi criado, mesmo que o encerramento esteja em um escopo diferente quando é executado — você viu um exemplo disso já com funções aninhadas. Você pode escrever um fechamento sem nome colocando o código entre colchetes ( {} ). Use in para separar os argumentos e o tipo de retorno do corpo.

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.

Você tem várias opções para escrever encerramentos de forma mais concisa. Quando o tipo de um encerramento já é conhecido, como o retorno de chamada de um delegado, você pode omitir o tipo de seus parâmetros, seu tipo de retorno ou ambos. Os fechamentos de instrução única retornam implicitamente o valor de sua única instrução.

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

Você pode referir-se aos parâmetros por número em vez de por nome — esta abordagem é especialmente útil em encerramentos muito curtos. Um fechamento passado como último argumento para uma função pode aparecer imediatamente após os parênteses. Quando um fechamento é o único argumento de uma função, você pode omitir totalmente os parênteses.

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

Objetos e Classes

Use class seguido do nome da classe para criar uma classe. Uma declaração de propriedade em uma classe é escrita da mesma forma que uma declaração de constante ou variável, exceto que está no contexto de uma classe. Da mesma forma, as declarações de métodos e funções são escritas da mesma maneira.

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.

Crie uma instância de uma classe colocando parênteses após o nome da classe. Use a sintaxe de ponto para acessar as propriedades e métodos da instância.

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

Esta versão da classe Shape está faltando algo importante: um inicializador para configurar a classe quando uma instância é criada. Use init para criar um.

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 como self é usado para distinguir a propriedade name do argumento name do inicializador. Os argumentos para o inicializador são passados ​​como uma chamada de função quando você cria uma instância da classe. Toda propriedade precisa de um valor atribuído — seja em sua declaração (como em numberOfSides ) ou no inicializador (como em name ).

Use deinit para criar um desinicializador se precisar realizar alguma limpeza antes de o objeto ser desalocado.

As subclasses incluem o nome da superclasse após o nome da classe, separados por dois pontos. Não há nenhum requisito para que as classes subclassifiquem qualquer classe raiz padrão, portanto você pode incluir ou omitir uma superclasse conforme necessário.

Os métodos em uma subclasse que substituem a implementação da superclasse são marcados com override — substituir um método por acidente, sem override , é detectado pelo compilador como um erro. O compilador também detecta métodos com override que na verdade não substituem nenhum método na superclasse.

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.

Além das propriedades simples que são armazenadas, as propriedades podem ter um getter e um 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

No setter de perimeter , o novo valor tem o nome implícito newValue . Você pode fornecer um nome explícito entre parênteses após set .

Observe que o inicializador da classe EquilateralTriangle possui três etapas diferentes:

  1. Definir o valor das propriedades que a subclasse declara.

  2. Chamando o inicializador da superclasse.

  3. Alterando o valor das propriedades definidas pela superclasse. Qualquer trabalho de configuração adicional que utilize métodos, getters ou setters também pode ser feito neste momento.

Se você não precisar calcular a propriedade, mas ainda precisar fornecer o código que será executado antes e depois de definir um novo valor, use willSet e didSet . O código que você fornece é executado sempre que o valor muda fora de um inicializador. Por exemplo, a classe abaixo garante que o comprimento do lado do seu triângulo seja sempre igual ao comprimento do lado do seu quadrado.

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

Ao trabalhar com valores opcionais, você pode escrever ? antes de operações como métodos, propriedades e subscritos. Se o valor antes do ? é nil , tudo depois do ? é ignorado e o valor de toda a expressão é nil . Caso contrário, o valor opcional será desembrulhado e tudo após o ? atua sobre o valor desembrulhado. Em ambos os casos, o valor de toda a expressão é um valor opcional.

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

Enumerações e Estruturas

Use enum para criar uma enumeração. Assim como as classes e todos os outros tipos nomeados, as enumerações podem ter métodos associados a elas.

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.

Por padrão, o Swift atribui os valores brutos começando em zero e aumentando um a cada vez, mas você pode alterar esse comportamento especificando explicitamente os valores. No exemplo acima, Ace recebe explicitamente um valor bruto de 1 e o restante dos valores brutos são atribuídos em ordem. Você também pode usar strings ou números de ponto flutuante como tipo bruto de uma enumeração. Use a propriedade rawValue para acessar o valor bruto de um caso de enumeração.

Use o inicializador init?(rawValue:) para criar uma instância de uma enumeração a partir de um valor bruto. Ele retorna o caso de enumeração correspondente ao valor bruto ou nil se não houver Rank correspondente.

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

Os valores de caso de uma enumeração são valores reais, e não apenas outra forma de escrever seus valores brutos. Na verdade, nos casos em que não há um valor bruto significativo, não é necessário fornecê-lo.

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 as duas maneiras pelas quais o caso Hearts da enumeração é referido acima: Ao atribuir um valor à constante hearts , o caso de enumeração Suit.Hearts é referido pelo seu nome completo porque a constante não tem um tipo explícito especificado. Dentro do switch, o caso de enumeração é referido pela forma abreviada .Hearts porque o valor de self já é conhecido como naipe. Você pode usar a forma abreviada sempre que o tipo do valor já for conhecido.

Se uma enumeração tiver valores brutos, esses valores serão determinados como parte da declaração, o que significa que cada instância de um caso de enumeração específico sempre terá o mesmo valor bruto. Outra opção para casos de enumeração é ter valores associados ao caso — esses valores são determinados quando você cria a instância e podem ser diferentes para cada instância de um caso de enumeração. Você pode pensar nos valores associados como se comportando como propriedades armazenadas da instância do caso de enumeração.

Por exemplo, considere o caso de solicitar os horários do nascer e do pôr do sol de um servidor. O servidor responde com as informações solicitadas ou com uma descrição do que deu errado.

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 como os horários do nascer e do pôr do sol são extraídos do valor ServerResponse como parte da correspondência do valor com os casos de switch.

Use struct para criar uma estrutura. As estruturas suportam muitos dos mesmos comportamentos das classes, incluindo métodos e inicializadores. Uma das diferenças mais importantes entre estruturas e classes é que as estruturas são sempre copiadas quando são passadas no seu código, mas as classes são passadas por referência.

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 e Extensões

Use protocol para declarar um protocolo.

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

Classes, enumerações e estruturas podem adotar 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 o uso da palavra-chave mutating na declaração de SimpleStructure para marcar um método que modifica a estrutura. A declaração de SimpleClass não precisa de nenhum de seus métodos marcados como mutantes porque os métodos em uma classe sempre podem modificar a classe.

Use extension para adicionar funcionalidade a um tipo existente, como novos métodos e propriedades computadas. Você pode usar uma extensão para adicionar conformidade de protocolo a um tipo declarado em outro lugar ou até mesmo a um tipo importado de uma biblioteca ou estrutura.

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.

Você pode usar um nome de protocolo como qualquer outro tipo nomeado — por exemplo, para criar uma coleção de objetos que possuem tipos diferentes, mas que estão todos em conformidade com um único protocolo. Ao trabalhar com valores cujo tipo é um tipo de protocolo, os métodos fora da definição do protocolo não ficam disponíveis.

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

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

Mesmo que a variável protocolValue tenha um tipo de tempo de execução SimpleClass , o compilador a trata como o tipo fornecido de ExampleProtocol . Isso significa que você não pode acessar acidentalmente métodos ou propriedades que a classe implementa além de sua conformidade com o protocolo.

Tratamento de erros

Você representa erros usando qualquer tipo que adote o protocolo Error .

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

Use throw para lançar um erro e throws para marcar uma função que pode gerar um erro. Se você gerar um erro em uma função, a função retornará imediatamente e o código que a chamou tratará o erro.

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

Existem várias maneiras de lidar com erros. Uma maneira é usar do-catch . Dentro do bloco do , você marca o código que pode gerar um erro escrevendo try na frente dele. Dentro do bloco catch , o erro recebe automaticamente o nome error , a menos que você forneça um nome 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.

Você pode fornecer vários blocos catch que tratam de erros específicos. Você escreve um padrão após catch da mesma forma que faz após case em um switch.

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?

Outra maneira de lidar com erros é usar try? para converter o resultado em opcional. Se a função gerar um erro, o erro específico será descartado e o resultado será nil . Caso contrário, o resultado será um opcional contendo o valor retornado pela função.

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

Use defer para escrever um bloco de código que é executado após todos os outros códigos da função, pouco antes do retorno da função. O código é executado independentemente de a função gerar um erro. Você pode usar defer para escrever códigos de configuração e limpeza lado a lado, mesmo que eles precisem ser executados em 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

Escreva um nome entre colchetes angulares para criar uma função ou 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"]

Você pode criar formas genéricas de funções e métodos, bem como classes, enumerações e estruturas.

// 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 após o nome do tipo para especificar uma lista de requisitos — por exemplo, para exigir que o tipo implemente um protocolo, para exigir que dois tipos sejam iguais ou para exigir que uma classe tenha uma superclasse específica.

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

Escrever <T: Equatable> é o mesmo que escrever <T> ... where T: Equatable .