Esta página foi traduzida pela API Cloud Translation.
Switch to English

Um passeio rápido

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

A tradição sugere que o primeiro programa em um novo idioma 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, essa sintaxe lhe parece familiar - no 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, assim 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 no 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 passeio é 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 exatamente uma vez. Isso significa que você pode usar constantes para nomear um valor que você determina uma vez, mas que usa em muitos lugares.

 var myVariable = 42
myVariable = 50
let myConstant = 42
 

Uma constante ou variável deve ter o mesmo tipo que o valor que você deseja atribuir a ele. No entanto, você nem sempre precisa escrever o tipo explicitamente. Fornecer um valor quando você cria uma constante ou variável permite que o compilador deduza seu tipo. No exemplo acima, o compilador deduz 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 vez de Float para números de ponto flutuante fornece 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 implicitamente convertidos para outro tipo. Se você precisar converter um valor para um tipo diferente, faça explicitamente uma instância do tipo desejado.

 let label = "The width is "
let width = 94
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 escreva uma barra invertida (``) antes dos parênteses. Por exemplo:

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

 let oranges = 5
"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 seqüências de caracteres que ocupam várias linhas. A indentação no início de cada linha entre aspas é removida, desde que corresponda à indentação das aspas finais. 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 matrizes e dicionários usando colchetes ( [] ) e acesse seus elementos escrevendo o índice ou a 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"
occupations
 
▿ 3 elements
  ▿ 0 : 2 elements

    - key : "Malcolm"
    - value : "Captain"
  ▿ 1 : 2 elements
    - key : "Jayne"
    - value : "Public Relations"
  ▿ 2 : 2 elements
    - key : "Kaylee"
    - value : "Mechanic"

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

 shoppingList.append("blue paint")
shoppingList
 
▿ 5 elements

  - 0 : "catfish"
  - 1 : "bottle of water"
  - 2 : "tulips"
  - 3 : "blue paint"
  - 4 : "blue paint"

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

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

Se as informações de tipo puderem ser deduzidas, você poderá escrever uma matriz vazia como [] e um dicionário vazio como [:] - por exemplo, quando definir um novo valor para uma variável ou passar 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 criar loops. Parênteses em torno da variável de condição ou loop são opcionais. Aparelhos ao redor do corpo são necessários.

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

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

Você pode usar if e let em conjunto para trabalhar com valores que podem estar ausentes. Esses valores são representados como opcionais. Um valor opcional contém um valor ou nil para indicar que está faltando um valor. Escreva um ponto de interrogação ( ? ) Após o tipo de um valor para marcar o valor como opcional.

 var optionalString: String? = "Hello"
optionalString == nil
 
false

 var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
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 , o condicional é false e o código entre chaves é 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"
"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 se limitam 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 a essa parte de um padrão a uma constante.

Depois de executar o código dentro da caixa do switch correspondente, o programa sai da instrução switch. A execução não continua no 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 de um dicionário, fornecendo um par de nomes 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
        }
    }
}
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 seja alterada. 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
}

n
 
128

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

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
}

total
 
6

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

Funções e Encerramentos

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)."
}
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 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)."
}
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 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

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 que seja longa ou complexa.

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

Funções são do 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()
increment(7)
 
8

Uma função pode assumir 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]
hasAnyMatches(list: numbers, condition: lessThanTen)
 
true

Funções são, na verdade, um caso especial de fechamento: blocos de código que podem ser chamados mais tarde. O código em um fechamento tem acesso a coisas como variáveis ​​e funções que estavam disponíveis no escopo em que o fechamento foi criado, mesmo que o fechamento 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 chaves ( {} ). Use in separar os argumentos e tipo de retorno do corpo.

 numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})
 
▿ 4 elements

  - 0 : 60
  - 1 : 57
  - 2 : 21
  - 3 : 36

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

Você tem várias opções para escrever fechamentos de forma mais concisa. Quando o tipo de fechamento 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. Encerramentos de instrução única retornam implicitamente o valor de sua única instrução.

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

Você pode consultar os parâmetros por número em vez de por nome - essa 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 fechamento é o único argumento para uma função, você pode omitir completamente os parênteses.

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

Objetos e Classes

Use class seguida pelo nome da classe para criar uma classe. Uma declaração de propriedade em uma classe é escrita da mesma maneira que uma declaração 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()
 

Está faltando algo importante nesta versão da classe Shape : 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 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. Toda propriedade precisa de um valor atribuído - em sua declaração (como em numberOfSides ) ou no inicializador (como em name ).

Use deinit para criar um desinicializador, se você precisar executar alguma limpeza antes que o objeto seja desalocado.

As subclasses incluem o nome da superclasse após o nome da classe, separadas por dois pontos. Não há requisito para que as classes subclasses 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 - override 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")
test.area()
test.simpleDescription()
 
"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 para perimeter , o novo valor tem o nome implícito newValue . Você pode fornecer um nome explícito entre parênteses após o set .

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

  1. Configurando 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 realizado neste momento.

Se você não precisar calcular a propriedade, mas ainda precisar fornecer o código que é 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 lateral de seu triângulo seja sempre o mesmo que o comprimento lateral de 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 assinatura. 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 depois do ? atua sobre o valor desembrulhado. Nos dois casos, o valor de toda a expressão é um valor opcional.

 let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
optionalSquare?.sideLength
 
▿ Optional<Double>

  - some : 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 um a cada vez, mas você pode alterar esse comportamento especificando explicitamente os valores. No exemplo acima, o Ace recebe explicitamente um valor bruto de 1 e o restante dos valores brutos é atribuído em ordem. Você também pode usar seqüências de caracteres 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. Retorna o caso de enumeração correspondente ao valor bruto ou nil se não houver uma 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. De fato, 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 é mencionado acima: Ao atribuir um valor à constante hearts , o caso de enumeração Suit.Hearts é referido por seu nome completo, porque a constante não possui um tipo explícito especificado. Dentro do comutador, o caso de enumeração é referido no formato abreviado .Hearts porque o valor de self já é conhecido por ser um naipe. Você pode usar o formulário abreviado 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 todas as instâncias de um caso de enumeração específico sempre terão 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 de 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 comparação do valor com os casos do comutador.

Use struct para criar uma estrutura. As estruturas suportam muitos dos mesmos comportamentos que as classes, incluindo métodos e inicializadores. Uma das diferenças mais importantes entre estruturas e classes é que as estruturas são sempre copiadas quando 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()
b.adjust()
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 mutating palavra-chave na declaração de SimpleStructure para marcar um método que modifica a estrutura. A declaração do 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 a extension para adicionar funcionalidade a um tipo existente, como novos métodos e propriedades calculadas. Você pode usar uma extensão para adicionar conformidade de protocolo a um tipo declarado em outro lugar ou 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
    }
}
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 tenham tipos diferentes, mas que estejam em conformidade com um único protocolo. Quando você trabalha com valores cujo tipo é um tipo de protocolo, métodos fora da definição de protocolo não estão disponíveis.

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

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

Embora 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 da conformidade com o protocolo.

Manipulação 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 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 manipulará 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 error nome, error menos que você atribua 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 de catch que lidam com erros específicos. Você escreve um padrão após catch da mesma forma que após case em um comutador.

 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 um 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 que contém 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 após todos os outros códigos da função, imediatamente 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ódigos de instalação e limpeza próximos um 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
}
fridgeContains("banana")
fridgeIsOpen
 
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
}
makeArray(repeating: "knock", numberOfTimes: 4)
 
▿ 4 elements

  - 0 : "knock"
  - 1 : "knock"
  - 2 : "knock"
  - 3 : "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
}
anyCommonElements([1, 2, 3], [3])
 
true

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