RSVP para seu evento TensorFlow Everywhere hoje mesmo!
Esta página foi traduzida pela API Cloud Translation.
Switch to English

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 no TensorFlow.org Executar no Google Colab Ver fonte no GitHub

A tradição sugere que o primeiro programa em um novo idioma deve imprimir as palavras "Olá, mundo!" na tela. Em Swift, isso pode ser feito em uma única linha:

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

Se você escreveu código em C ou Objective-C, essa sintaxe parece familiar para você - em Swift, essa 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 no escopo global é usado como ponto de entrada para o programa, então 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 você não entender alguma coisa - tudo apresentado neste tour é explicado em detalhes no restante deste livro.

Valores Simples

Use let para fazer uma constante e var para fazer uma variável. O valor de uma constante não precisa ser conhecido no momento da 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, você nem sempre precisa escrever o tipo explicitamente. Fornecer um valor ao criar uma constante ou variável permite que o compilador inferir seu tipo. No exemplo acima, o compilador infere que myVariable é um inteiro porque seu valor inicial é um inteiro.

Se o valor inicial não fornecer informações suficientes (ou se não houver um valor inicial), especifique o tipo escrevendo-o após a variável, separados por dois pontos. Nota: usar Double 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 em outro tipo. Se você precisar converter um valor em 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"]

Os arrays aumentam automaticamente conforme você adiciona elementos.

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

Para criar uma matriz ou dicionário vazio, use a sintaxe do inicializador.

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

Se a informação do tipo puder ser inferida, você pode escrever uma matriz vazia 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 = [:]

Controle de fluxo

Use if e switch para criar condicionais e use for - in , for , while e repeat - while para fazer loops. Os parênteses em torno da condição ou variável de loop são opcionais. São necessários cintas à volta 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 o 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 podem 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á ausente. Escreva um ponto de interrogação ( ? ) Após o tipo de um valor para marcar o 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`.

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 torna o valor desembrulhado disponível 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 ausente, 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 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 a essa parte de um padrão a uma constante.

Depois de executar o código dentro do caso de switch 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 chave no final do código de cada caso.

Você usa for - in para iterar itens em um dicionário, fornecendo um par de nomes a serem usados ​​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 uma 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 ser 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 fazer 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 fazer um intervalo que omite seu valor superior e use ... para fazer 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 de 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âmetro como rótulos para seus argumentos. Escreva um rótulo de argumento customizado 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 fazer um valor composto - por exemplo, para retornar vários valores de uma função. Os elementos de uma tupla podem ser referenciados por nome ou 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

As funções são um tipo de primeira classe. Isso significa que uma função pode retornar outra função como seu 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 ter 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 se o encerramento estiver em um escopo diferente quando for executado - você viu um exemplo disso já com funções aninhadas. Você pode escrever um encerramento sem um 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 uma closure já é conhecido, como o retorno de chamada para um delegado, você pode omitir o tipo de seus parâmetros, seu tipo de retorno ou ambos. O encerramento de uma única instrução retorna implicitamente o valor de sua única instrução.

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

Você pode se referir a parâmetros por número em vez de por nome - esta abordagem é especialmente útil em fechamentos muito curtos. Um fechamento passado como o último argumento para uma função pode aparecer imediatamente após os parênteses. Quando um encerramento é o único argumento para 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 maneira 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étodo e função 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 o 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 o name da propriedade do name argumento para o inicializador. Os argumentos para o inicializador são passados ​​como uma chamada de função quando você cria uma instância da classe. Cada propriedade precisa de um valor atribuído - em sua declaração (como com numberOfSides ) ou no inicializador (como com name ).

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

As subclasses incluem seus nomes de superclasse após o nome da classe, separados por dois pontos. Não há requisitos para as classes criarem subclasses de 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 ignorar 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 da 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 configurador de perimeter , o novo valor possui o nome implícito newValue . Você pode fornecer um nome explícito entre parênteses após set .

Observe que o inicializador para a classe EquilateralTriangle tem três etapas diferentes:

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

  2. Chamando o inicializador da superclasse.

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

Se você não precisa calcular a propriedade, mas ainda precisa fornecer o código que é executado antes e depois de definir um novo valor, use willSet e didSet . O código fornecido é executado sempre que o valor muda fora de um inicializador. Por exemplo, a classe abaixo garante que o comprimento do lado do triângulo seja sempre igual ao comprimento do lado do 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 é desembrulhado, e tudo após o ? atua no 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. 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 incrementando em 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 o tipo bruto de uma enumeração. Use a propriedade rawValue para acessar o valor bruto de um caso de enumeração.

Use o init?(rawValue:) para criar uma instância de uma enumeração a partir de um valor bruto. Ele retorna o caso de enumeração que corresponde 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, não apenas outra maneira de escrever seus valores brutos. Na verdade, nos casos em que não há um valor bruto significativo, você não precisa fornecer um.

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 de 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 da opção, o caso de enumeração é referido pela forma abreviada .Hearts porque o valor de self já é conhecido como um 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 tem 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 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 de nascer e 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 transmitidas em seu código, mas as classes são transmitidas 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 o 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 de uma classe sempre podem modificar a classe.

Use a 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 mesmo a um tipo que você importou 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 em conformidade com um único protocolo. Quando você trabalha com valores cujo tipo é um tipo de protocolo, os métodos fora da definição do protocolo não estão 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 de protocolo.

Manipulação de erros

Você representa erros usando qualquer tipo que adote o protocolo de 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 lançar um erro. Se você lançar um erro em uma função, a função retornará imediatamente e o código que chamou a função 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 os 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 menos que você dê a ele 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 exatamente como 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 os 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 é um opcional contendo o valor que a função retornou.

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 depois de todos os outros códigos na função, logo antes do retorno da função. O código é executado independentemente de a função gerar um erro. Você pode usar o defer para escrever código de configuração e limpeza um ao lado do outro, 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 .