Une tournée rapide

Adapté de l'original A Swift Tour sur Swift.org avec des modifications. Le contenu original a été rédigé par Apple Inc. Sous licence Creative Commons Attribution 4.0 International (CC BY 4.0) .
Voir sur TensorFlow.org Afficher la source sur GitHub

La tradition suggère que le premier programme dans une nouvelle langue devrait imprimer les mots « Hello, world ! » sur l'écran. Dans Swift, cela peut être fait en une seule ligne :

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

Si vous avez écrit du code en C ou en Objective-C, cette syntaxe vous semble familière : dans Swift, cette ligne de code est un programme complet. Vous n'avez pas besoin d'importer une bibliothèque distincte pour des fonctionnalités telles que les entrées/sorties ou la gestion des chaînes. Le code écrit à portée globale est utilisé comme point d'entrée pour le programme, vous n'avez donc pas besoin d'une fonction main() . Vous n’avez pas non plus besoin d’écrire des points-virgules à la fin de chaque instruction.

Cette visite guidée vous donne suffisamment d'informations pour commencer à écrire du code dans Swift en vous montrant comment accomplir diverses tâches de programmation. Ne vous inquiétez pas si vous ne comprenez pas quelque chose : tout ce qui est présenté dans cette visite est expliqué en détail dans le reste de ce livre.

Valeurs simples

Utilisez let pour créer une constante et var pour créer une variable. La valeur d'une constante n'a pas besoin d'être connue au moment de la compilation, mais vous devez lui attribuer une valeur exactement une fois. Cela signifie que vous pouvez utiliser des constantes pour nommer une valeur que vous déterminez une fois mais que vous utilisez à plusieurs endroits.

var myVariable = 42
myVariable = 50
let myConstant = 42

Une constante ou une variable doit avoir le même type que la valeur que vous souhaitez lui attribuer. Cependant, vous n'êtes pas toujours obligé d'écrire le type explicitement. Fournir une valeur lorsque vous créez une constante ou une variable permet au compilateur de déduire son type. Dans l'exemple ci-dessus, le compilateur déduit que myVariable est un entier car sa valeur initiale est un entier.

Si la valeur initiale ne fournit pas suffisamment d'informations (ou s'il n'y a pas de valeur initiale), spécifiez le type en l'écrivant après la variable, séparé par deux points. Remarque : utiliser Double au lieu de Float pour les nombres à virgule flottante vous donne plus de précision et constitue le type à virgule flottante par défaut dans 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.

Les valeurs ne sont jamais implicitement converties en un autre type. Si vous devez convertir une valeur en un type différent, créez explicitement une instance du type souhaité.

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?

Il existe un moyen encore plus simple d'inclure des valeurs dans des chaînes : écrivez la valeur entre parenthèses et écrivez une barre oblique inverse (``) avant les parenthèses. Par exemple:

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.

Utilisez trois guillemets doubles ( """ ) pour les chaînes qui occupent plusieurs lignes. L'indentation au début de chaque ligne entre guillemets est supprimée, à condition qu'elle corresponde à l'indentation des guillemets fermants. Par exemple :

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.

Créez des tableaux et des dictionnaires en utilisant des crochets ( [] ) et accédez à leurs éléments en écrivant l'index ou la clé entre parenthèses. Une virgule est autorisée après le dernier élément.

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"]

Les tableaux s'agrandissent automatiquement à mesure que vous ajoutez des éléments.

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

Pour créer un tableau ou un dictionnaire vide, utilisez la syntaxe d'initialisation.

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

Si les informations de type peuvent être déduites, vous pouvez écrire un tableau vide sous la forme [] et un dictionnaire vide sous la forme [:] — par exemple, lorsque vous définissez une nouvelle valeur pour une variable ou transmettez un argument à une fonction.

shoppingList = []
occupations = [:]

Flux de contrôle

Utilisez if et switch pour créer des conditions, et utilisez for - in , for , while et repeat - while pour créer des boucles. Les parenthèses autour de la condition ou de la variable de boucle sont facultatives. Des appareils orthodontiques autour du corps sont nécessaires.

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

Dans une instruction if , le conditionnel doit être une expression booléenne : cela signifie qu'un code tel que if score { ... } est une erreur et non une comparaison implicite à zéro.

Vous pouvez utiliser if et let ensemble pour travailler avec des valeurs qui pourraient manquer. Ces valeurs sont représentées comme facultatives. Une valeur facultative contient une valeur ou contient nil pour indiquer qu'une valeur est manquante. Écrivez un point d'interrogation ( ? ) après le type d'une valeur pour marquer la valeur comme facultative.

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

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

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

Si la valeur facultative est nil , le conditionnel est false et le code entre accolades est ignoré. Sinon, la valeur facultative est déballée et affectée à la constante après let , ce qui rend la valeur déballée disponible dans le bloc de code.

Une autre façon de gérer les valeurs facultatives consiste à fournir une valeur par défaut à l'aide du ?? opérateur. Si la valeur facultative est manquante, la valeur par défaut est utilisée à la place.

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

Les commutateurs prennent en charge tout type de données et une grande variété d'opérations de comparaison. Ils ne se limitent pas aux nombres entiers et aux tests d'égalité.

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?

Remarquez comment let peut être utilisé dans un modèle pour attribuer la valeur qui correspond à cette partie d'un modèle à une constante.

Après avoir exécuté le code dans le cas switch qui correspond, le programme quitte l'instruction switch. L'exécution ne continue pas jusqu'au cas suivant, il n'est donc pas nécessaire de quitter explicitement le commutateur à la fin du code de chaque cas.

Vous utilisez for - in pour parcourir les éléments d'un dictionnaire en fournissant une paire de noms à utiliser pour chaque paire clé-valeur. Les dictionnaires sont une collection non ordonnée, donc leurs clés et valeurs sont itérées dans un ordre arbitraire.

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.

Utilisez while pour répéter un bloc de code jusqu'à ce qu'une condition change. La condition d'une boucle peut être à la fin, garantissant que la boucle est exécutée au moins une fois.

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

Vous pouvez conserver un index dans une boucle, soit en utilisant ..< pour créer une plage d'index, soit en écrivant une initialisation, une condition et un incrément explicites. Ces deux boucles font la même chose :

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

print(total)
6

Utilisez ..< pour créer une plage qui omet sa valeur supérieure et utilisez ... pour créer une plage qui inclut les deux valeurs.

Fonctions et fermetures

Utilisez func pour déclarer une fonction. Appelez une fonction en faisant suivre son nom d'une liste d'arguments entre parenthèses. Utilisez -> pour séparer les noms et types de paramètres du type de retour de la fonction.

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.

Par défaut, les fonctions utilisent leurs noms de paramètres comme étiquettes pour leurs arguments. Écrivez une étiquette d'argument personnalisée avant le nom du paramètre ou écrivez _ pour n'utiliser aucune étiquette d'argument.

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

Utilisez un tuple pour créer une valeur composée, par exemple pour renvoyer plusieurs valeurs à partir d'une fonction. Les éléments d'un tuple peuvent être désignés soit par leur nom, soit par leur numéro.

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

Les fonctions peuvent être imbriquées. Les fonctions imbriquées ont accès aux variables déclarées dans la fonction externe. Vous pouvez utiliser des fonctions imbriquées pour organiser le code dans une fonction longue ou complexe.

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

Les fonctions sont un type de première classe. Cela signifie qu'une fonction peut renvoyer une autre fonction comme valeur.

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

Une fonction peut prendre une autre fonction comme argument.

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

Les fonctions sont en fait un cas particulier de fermetures : des blocs de code qui peuvent être appelés ultérieurement. Le code d'une fermeture a accès à des éléments tels que des variables et des fonctions qui étaient disponibles dans la portée où la fermeture a été créée, même si la fermeture se trouve dans une portée différente lors de son exécution (vous en avez déjà vu un exemple avec des fonctions imbriquées). Vous pouvez écrire une fermeture sans nom en entourant le code d'accolades ( {} ). Utilisez in pour séparer les arguments et le type de retour du corps.

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.

Vous disposez de plusieurs options pour rédiger les fermetures de manière plus concise. Lorsque le type d'une fermeture est déjà connu, comme le rappel d'un délégué, vous pouvez omettre le type de ses paramètres, son type de retour, ou les deux. Les fermetures d'instructions uniques renvoient implicitement la valeur de leur seule instruction.

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

Vous pouvez faire référence aux paramètres par numéro plutôt que par nom : cette approche est particulièrement utile dans les fermetures très courtes. Une fermeture passée en dernier argument d'une fonction peut apparaître immédiatement après les parenthèses. Lorsqu'une fermeture est le seul argument d'une fonction, vous pouvez omettre entièrement les parenthèses.

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

Objets et classes

Utilisez class suivi du nom de la classe pour créer une classe. Une déclaration de propriété dans une classe s'écrit de la même manière qu'une déclaration de constante ou de variable, sauf qu'elle s'effectue dans le contexte d'une classe. De même, les déclarations de méthodes et de fonctions sont écrites de la même manière.

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.

Créez une instance d'une classe en mettant des parenthèses après le nom de la classe. Utilisez la syntaxe par points pour accéder aux propriétés et méthodes de l'instance.

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

Il manque quelque chose d'important dans cette version de la classe Shape : un initialiseur pour configurer la classe lors de la création d'une instance. Utilisez init pour en créer un.

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

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

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

Remarquez comment self est utilisé pour distinguer la propriété name de l'argument name de l'initialiseur. Les arguments de l'initialiseur sont transmis comme un appel de fonction lorsque vous créez une instance de la classe. Chaque propriété nécessite une valeur attribuée, soit dans sa déclaration (comme avec numberOfSides ), soit dans l'initialiseur (comme avec name ).

Utilisez deinit pour créer un désinitialiseur si vous devez effectuer un nettoyage avant que l'objet ne soit désalloué.

Les sous-classes incluent leur nom de superclasse après leur nom de classe, séparé par deux points. Il n'est pas nécessaire que les classes sous-classent une classe racine standard, vous pouvez donc inclure ou omettre une superclasse selon vos besoins.

Les méthodes d'une sous-classe qui remplacent l'implémentation de la superclasse sont marquées par override : le remplacement d'une méthode par accident, sans override , est détecté par le compilateur comme une erreur. Le compilateur détecte également les méthodes avec override qui ne remplacent aucune méthode de la 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.

En plus des propriétés simples stockées, les propriétés peuvent avoir un getter et un setter.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

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

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

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

Dans le paramètre de perimeter , la nouvelle valeur porte le nom implicite newValue . Vous pouvez fournir un nom explicite entre parenthèses après set .

Notez que l'initialiseur de la classe EquilateralTriangle comporte trois étapes différentes :

  1. Définition de la valeur des propriétés déclarées par la sous-classe.

  2. Appel de l'initialiseur de la superclasse.

  3. Modification de la valeur des propriétés définies par la superclasse. Tout travail de configuration supplémentaire utilisant des méthodes, des getters ou des setters peut également être effectué à ce stade.

Si vous n'avez pas besoin de calculer la propriété mais que vous devez quand même fournir du code exécuté avant et après la définition d'une nouvelle valeur, utilisez willSet et didSet . Le code que vous fournissez est exécuté chaque fois que la valeur change en dehors d'un initialiseur. Par exemple, la classe ci-dessous garantit que la longueur du côté de son triangle est toujours la même que la longueur du côté de son carré.

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

Lorsque vous travaillez avec des valeurs facultatives, vous pouvez écrire ? avant les opérations telles que les méthodes, les propriétés et les indices. Si la valeur avant le ? est nil , tout après le ? est ignoré et la valeur de l'expression entière est nil . Sinon, la valeur facultative est déballée, et tout ce qui se trouve après le ? agit sur la valeur non emballée. Dans les deux cas, la valeur de l’expression entière est une valeur facultative.

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

Énumérations et structures

Utilisez enum pour créer une énumération. Comme les classes et tous les autres types nommés, les énumérations peuvent être associées à des méthodes.

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.

Par défaut, Swift attribue les valeurs brutes en commençant à zéro et en incrémentant de un à chaque fois, mais vous pouvez modifier ce comportement en spécifiant explicitement des valeurs. Dans l'exemple ci-dessus, Ace reçoit explicitement une valeur brute de 1 et le reste des valeurs brutes est attribué dans l'ordre. Vous pouvez également utiliser des chaînes ou des nombres à virgule flottante comme type brut d'une énumération. Utilisez la propriété rawValue pour accéder à la valeur brute d'un cas d'énumération.

Utilisez l'initialiseur init?(rawValue:) pour créer une instance d'une énumération à partir d'une valeur brute. Il renvoie soit le cas d'énumération correspondant à la valeur brute, soit nil s'il n'y a pas Rank correspondant.

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

Les valeurs de cas d'une énumération sont des valeurs réelles, et non simplement une autre façon d'écrire leurs valeurs brutes. En fait, dans les cas où il n’existe pas de valeur brute significative, vous n’êtes pas obligé d’en fournir une.

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.

Notez les deux manières dont le cas Hearts de l'énumération est mentionné ci-dessus : Lors de l'attribution d'une valeur à la constante hearts , le cas d'énumération Suit.Hearts est désigné par son nom complet car la constante n'a pas de type explicite spécifié. À l'intérieur du commutateur, le cas d'énumération est désigné par la forme abrégée .Hearts car la valeur de self est déjà connue pour être un costume. Vous pouvez utiliser la forme abrégée chaque fois que le type de la valeur est déjà connu.

Si une énumération comporte des valeurs brutes, ces valeurs sont déterminées dans le cadre de la déclaration, ce qui signifie que chaque instance d'un cas d'énumération particulier a toujours la même valeur brute. Un autre choix pour les cas d'énumération consiste à associer des valeurs au cas : ces valeurs sont déterminées lorsque vous créez l'instance et elles peuvent être différentes pour chaque instance d'un cas d'énumération. Vous pouvez considérer les valeurs associées comme se comportant comme des propriétés stockées de l’instance de cas d’énumération.

Par exemple, considérons le cas de la demande des heures de lever et de coucher du soleil à un serveur. Le serveur répond soit avec les informations demandées, soit en décrivant ce qui ne va pas.

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.

Remarquez comment les heures de lever et de coucher du soleil sont extraites de la valeur ServerResponse dans le cadre de la comparaison de la valeur avec les cas de commutation.

Utilisez struct pour créer une structure. Les structures prennent en charge bon nombre des mêmes comportements que les classes, y compris les méthodes et les initialiseurs. L'une des différences les plus importantes entre les structures et les classes est que les structures sont toujours copiées lorsqu'elles sont transmises dans votre code, mais les classes sont transmises par référence.

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.

Protocoles et extensions

Utilisez protocol pour déclarer un protocole.

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

Les classes, les énumérations et les structures peuvent toutes adopter des protocoles.

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?

Notez l'utilisation du mot-clé mutating dans la déclaration de SimpleStructure pour marquer une méthode qui modifie la structure. La déclaration de SimpleClass n'a besoin d'aucune de ses méthodes marquées comme en mutation car les méthodes d'une classe peuvent toujours modifier la classe.

Utilisez extension pour ajouter des fonctionnalités à un type existant, telles que de nouvelles méthodes et propriétés calculées. Vous pouvez utiliser une extension pour ajouter une conformité de protocole à un type déclaré ailleurs, ou même à un type que vous avez importé depuis une bibliothèque ou un framework.

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.

Vous pouvez utiliser un nom de protocole comme n'importe quel autre type nommé, par exemple pour créer une collection d'objets de types différents mais tous conformes à un seul protocole. Lorsque vous travaillez avec des valeurs dont le type est un type de protocole, les méthodes extérieures à la définition du protocole ne sont pas disponibles.

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

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

Même si la variable protocolValue a un type d'exécution SimpleClass , le compilateur la traite comme le type donné de ExampleProtocol . Cela signifie que vous ne pouvez pas accéder accidentellement aux méthodes ou propriétés que la classe implémente en plus de sa conformité au protocole.

La gestion des erreurs

Vous représentez les erreurs en utilisant n’importe quel type qui adopte le protocole Error .

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

Utilisez throw pour générer une erreur et throws pour marquer une fonction qui peut générer une erreur. Si vous lancez une erreur dans une fonction, la fonction est renvoyée immédiatement et le code qui a appelé la fonction gère l'erreur.

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

Il existe plusieurs façons de gérer les erreurs. Une façon consiste à utiliser do-catch . À l'intérieur du bloc do , vous marquez le code qui peut générer une erreur en écrivant try devant lui. À l’intérieur du bloc catch , l’erreur reçoit automatiquement le nom error sauf si vous lui donnez un nom différent.

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.

Vous pouvez fournir plusieurs blocs catch qui gèrent des erreurs spécifiques. Vous écrivez un modèle après catch comme vous le faites après case dans un 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?

Une autre façon de gérer les erreurs consiste à utiliser try? pour convertir le résultat en facultatif. Si la fonction renvoie une erreur, l'erreur spécifique est ignorée et le résultat est nil . Sinon, le résultat est un facultatif contenant la valeur renvoyée par la fonction.

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

Utilisez defer pour écrire un bloc de code qui est exécuté après tous les autres codes de la fonction, juste avant le retour de la fonction. Le code est exécuté même si la fonction génère une erreur. Vous pouvez utiliser defer pour écrire du code de configuration et de nettoyage côte à côte, même s'ils doivent être exécutés à des moments différents.

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

Génériques

Écrivez un nom entre crochets pour créer une fonction ou un type générique.

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"]

Vous pouvez créer des formes génériques de fonctions et de méthodes, ainsi que des classes, des énumérations et des structures.

// 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)

Utilisez where après le nom du type pour spécifier une liste d'exigences — par exemple, pour exiger que le type implémente un protocole, pour exiger que deux types soient identiques ou pour exiger qu'une classe ait une superclasse particulière.

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

Écrire <T: Equatable> revient à écrire <T> ... where T: Equatable .

,

Adapté de l'original A Swift Tour sur Swift.org avec des modifications. Le contenu original a été rédigé par Apple Inc. Sous licence Creative Commons Attribution 4.0 International (CC BY 4.0) .
Voir sur TensorFlow.org Afficher la source sur GitHub

La tradition suggère que le premier programme dans une nouvelle langue devrait imprimer les mots « Hello, world ! » sur l'écran. Dans Swift, cela peut être fait en une seule ligne :

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

Si vous avez écrit du code en C ou en Objective-C, cette syntaxe vous semble familière : dans Swift, cette ligne de code est un programme complet. Vous n'avez pas besoin d'importer une bibliothèque distincte pour des fonctionnalités telles que les entrées/sorties ou la gestion des chaînes. Le code écrit à portée globale est utilisé comme point d'entrée pour le programme, vous n'avez donc pas besoin d'une fonction main() . Vous n’avez pas non plus besoin d’écrire des points-virgules à la fin de chaque instruction.

Cette visite guidée vous donne suffisamment d'informations pour commencer à écrire du code dans Swift en vous montrant comment accomplir diverses tâches de programmation. Ne vous inquiétez pas si vous ne comprenez pas quelque chose : tout ce qui est présenté dans cette visite est expliqué en détail dans le reste de ce livre.

Valeurs simples

Utilisez let pour créer une constante et var pour créer une variable. La valeur d'une constante n'a pas besoin d'être connue au moment de la compilation, mais vous devez lui attribuer une valeur exactement une fois. Cela signifie que vous pouvez utiliser des constantes pour nommer une valeur que vous déterminez une fois mais que vous utilisez à plusieurs endroits.

var myVariable = 42
myVariable = 50
let myConstant = 42

Une constante ou une variable doit avoir le même type que la valeur que vous souhaitez lui attribuer. Cependant, vous n'êtes pas toujours obligé d'écrire le type explicitement. Fournir une valeur lorsque vous créez une constante ou une variable permet au compilateur de déduire son type. Dans l'exemple ci-dessus, le compilateur déduit que myVariable est un entier car sa valeur initiale est un entier.

Si la valeur initiale ne fournit pas suffisamment d'informations (ou s'il n'y a pas de valeur initiale), spécifiez le type en l'écrivant après la variable, séparé par deux points. Remarque : utiliser Double au lieu de Float pour les nombres à virgule flottante vous donne plus de précision et constitue le type à virgule flottante par défaut dans 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.

Les valeurs ne sont jamais implicitement converties en un autre type. Si vous devez convertir une valeur en un type différent, créez explicitement une instance du type souhaité.

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?

Il existe un moyen encore plus simple d'inclure des valeurs dans des chaînes : écrivez la valeur entre parenthèses et écrivez une barre oblique inverse (``) avant les parenthèses. Par exemple:

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.

Utilisez trois guillemets doubles ( """ ) pour les chaînes qui occupent plusieurs lignes. L'indentation au début de chaque ligne entre guillemets est supprimée, à condition qu'elle corresponde à l'indentation des guillemets fermants. Par exemple :

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.

Créez des tableaux et des dictionnaires en utilisant des crochets ( [] ) et accédez à leurs éléments en écrivant l'index ou la clé entre parenthèses. Une virgule est autorisée après le dernier élément.

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"]

Les tableaux s'agrandissent automatiquement à mesure que vous ajoutez des éléments.

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

Pour créer un tableau ou un dictionnaire vide, utilisez la syntaxe d'initialisation.

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

Si les informations de type peuvent être déduites, vous pouvez écrire un tableau vide sous la forme [] et un dictionnaire vide sous la forme [:] — par exemple, lorsque vous définissez une nouvelle valeur pour une variable ou transmettez un argument à une fonction.

shoppingList = []
occupations = [:]

Flux de contrôle

Utilisez if et switch pour créer des conditions, et utilisez for - in , for , while et repeat - while pour créer des boucles. Les parenthèses autour de la condition ou de la variable de boucle sont facultatives. Des appareils orthodontiques autour du corps sont nécessaires.

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

Dans une instruction if , le conditionnel doit être une expression booléenne : cela signifie qu'un code tel que if score { ... } est une erreur et non une comparaison implicite à zéro.

Vous pouvez utiliser if et let ensemble pour travailler avec des valeurs qui pourraient manquer. Ces valeurs sont représentées comme facultatives. Une valeur facultative contient une valeur ou contient nil pour indiquer qu'une valeur est manquante. Écrivez un point d'interrogation ( ? ) après le type d'une valeur pour marquer la valeur comme facultative.

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

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

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

Si la valeur facultative est nil , le conditionnel est false et le code entre accolades est ignoré. Sinon, la valeur facultative est déballée et affectée à la constante après let , ce qui rend la valeur déballée disponible dans le bloc de code.

Une autre façon de gérer les valeurs facultatives consiste à fournir une valeur par défaut à l'aide du ?? opérateur. Si la valeur facultative est manquante, la valeur par défaut est utilisée à la place.

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

Les commutateurs prennent en charge tout type de données et une grande variété d'opérations de comparaison. Ils ne se limitent pas aux nombres entiers et aux tests d'égalité.

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?

Remarquez comment let peut être utilisé dans un modèle pour attribuer la valeur qui correspond à cette partie d'un modèle à une constante.

Après avoir exécuté le code dans le cas switch qui correspond, le programme quitte l'instruction switch. L'exécution ne continue pas jusqu'au cas suivant, il n'est donc pas nécessaire de quitter explicitement le commutateur à la fin du code de chaque cas.

Vous utilisez for - in pour parcourir les éléments d'un dictionnaire en fournissant une paire de noms à utiliser pour chaque paire clé-valeur. Les dictionnaires sont une collection non ordonnée, donc leurs clés et valeurs sont itérées dans un ordre arbitraire.

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.

Utilisez while pour répéter un bloc de code jusqu'à ce qu'une condition change. La condition d'une boucle peut être à la fin, garantissant que la boucle est exécutée au moins une fois.

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

Vous pouvez conserver un index dans une boucle, soit en utilisant ..< pour créer une plage d'index, soit en écrivant une initialisation, une condition et un incrément explicites. Ces deux boucles font la même chose :

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

print(total)
6

Utilisez ..< pour créer une plage qui omet sa valeur supérieure et utilisez ... pour créer une plage qui inclut les deux valeurs.

Fonctions et fermetures

Utilisez func pour déclarer une fonction. Appelez une fonction en faisant suivre son nom d'une liste d'arguments entre parenthèses. Utilisez -> pour séparer les noms et types de paramètres du type de retour de la fonction.

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.

Par défaut, les fonctions utilisent leurs noms de paramètres comme étiquettes pour leurs arguments. Écrivez une étiquette d'argument personnalisée avant le nom du paramètre ou écrivez _ pour n'utiliser aucune étiquette d'argument.

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

Utilisez un tuple pour créer une valeur composée, par exemple pour renvoyer plusieurs valeurs à partir d'une fonction. Les éléments d'un tuple peuvent être désignés soit par leur nom, soit par leur numéro.

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

Les fonctions peuvent être imbriquées. Les fonctions imbriquées ont accès aux variables déclarées dans la fonction externe. Vous pouvez utiliser des fonctions imbriquées pour organiser le code dans une fonction longue ou complexe.

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

Les fonctions sont un type de première classe. Cela signifie qu'une fonction peut renvoyer une autre fonction comme valeur.

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

Une fonction peut prendre une autre fonction comme argument.

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

Les fonctions sont en fait un cas particulier de fermetures : des blocs de code qui peuvent être appelés ultérieurement. Le code d'une fermeture a accès à des éléments tels que des variables et des fonctions qui étaient disponibles dans la portée où la fermeture a été créée, même si la fermeture se trouve dans une portée différente lors de son exécution (vous en avez déjà vu un exemple avec des fonctions imbriquées). Vous pouvez écrire une fermeture sans nom en entourant le code d'accolades ( {} ). Utilisez in pour séparer les arguments et le type de retour du corps.

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.

Vous disposez de plusieurs options pour rédiger les fermetures de manière plus concise. Lorsque le type d'une fermeture est déjà connu, comme le rappel d'un délégué, vous pouvez omettre le type de ses paramètres, son type de retour, ou les deux. Les fermetures d'instructions uniques renvoient implicitement la valeur de leur seule instruction.

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

Vous pouvez faire référence aux paramètres par numéro plutôt que par nom : cette approche est particulièrement utile dans les fermetures très courtes. Une fermeture passée en dernier argument d'une fonction peut apparaître immédiatement après les parenthèses. Lorsqu'une fermeture est le seul argument d'une fonction, vous pouvez omettre entièrement les parenthèses.

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

Objets et classes

Utilisez class suivi du nom de la classe pour créer une classe. Une déclaration de propriété dans une classe s'écrit de la même manière qu'une déclaration de constante ou de variable, sauf qu'elle s'effectue dans le contexte d'une classe. De même, les déclarations de méthodes et de fonctions sont écrites de la même manière.

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.

Créez une instance d'une classe en mettant des parenthèses après le nom de la classe. Utilisez la syntaxe par points pour accéder aux propriétés et méthodes de l'instance.

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

Il manque quelque chose d'important dans cette version de la classe Shape : un initialiseur pour configurer la classe lors de la création d'une instance. Utilisez init pour en créer un.

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

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

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

Remarquez comment self est utilisé pour distinguer la propriété name de l'argument name de l'initialiseur. Les arguments de l'initialiseur sont transmis comme un appel de fonction lorsque vous créez une instance de la classe. Chaque propriété nécessite une valeur attribuée, soit dans sa déclaration (comme avec numberOfSides ), soit dans l'initialiseur (comme avec name ).

Utilisez deinit pour créer un désinitialiseur si vous devez effectuer un nettoyage avant que l'objet ne soit désalloué.

Les sous-classes incluent leur nom de superclasse après leur nom de classe, séparé par deux points. Il n'est pas nécessaire que les classes sous-classent une classe racine standard, vous pouvez donc inclure ou omettre une superclasse selon vos besoins.

Les méthodes d'une sous-classe qui remplacent l'implémentation de la superclasse sont marquées par override : le remplacement d'une méthode par accident, sans override , est détecté par le compilateur comme une erreur. Le compilateur détecte également les méthodes avec override qui ne remplacent aucune méthode de la 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.

En plus des propriétés simples stockées, les propriétés peuvent avoir un getter et un setter.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

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

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

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

Dans le paramètre de perimeter , la nouvelle valeur porte le nom implicite newValue . Vous pouvez fournir un nom explicite entre parenthèses après set .

Notez que l'initialiseur de la classe EquilateralTriangle comporte trois étapes différentes :

  1. Définition de la valeur des propriétés déclarées par la sous-classe.

  2. Appel de l'initialiseur de la superclasse.

  3. Modification de la valeur des propriétés définies par la superclasse. Tout travail de configuration supplémentaire utilisant des méthodes, des getters ou des setters peut également être effectué à ce stade.

Si vous n'avez pas besoin de calculer la propriété mais que vous devez quand même fournir du code exécuté avant et après la définition d'une nouvelle valeur, utilisez willSet et didSet . Le code que vous fournissez est exécuté chaque fois que la valeur change en dehors d'un initialiseur. Par exemple, la classe ci-dessous garantit que la longueur du côté de son triangle est toujours la même que la longueur du côté de son carré.

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

Lorsque vous travaillez avec des valeurs facultatives, vous pouvez écrire ? avant les opérations telles que les méthodes, les propriétés et les indices. Si la valeur avant le ? est nil , tout après le ? est ignoré et la valeur de l'expression entière est nil . Sinon, la valeur facultative est déballée, et tout ce qui se trouve après le ? agit sur la valeur non emballée. Dans les deux cas, la valeur de l’expression entière est une valeur facultative.

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

Énumérations et structures

Utilisez enum pour créer une énumération. Comme les classes et tous les autres types nommés, les énumérations peuvent être associées à des méthodes.

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.

Par défaut, Swift attribue les valeurs brutes en commençant à zéro et en incrémentant de un à chaque fois, mais vous pouvez modifier ce comportement en spécifiant explicitement des valeurs. Dans l'exemple ci-dessus, Ace reçoit explicitement une valeur brute de 1 et le reste des valeurs brutes est attribué dans l'ordre. Vous pouvez également utiliser des chaînes ou des nombres à virgule flottante comme type brut d'une énumération. Utilisez la propriété rawValue pour accéder à la valeur brute d'un cas d'énumération.

Utilisez l'initialiseur init?(rawValue:) pour créer une instance d'une énumération à partir d'une valeur brute. Il renvoie soit le cas d'énumération correspondant à la valeur brute, soit nil s'il n'y a pas Rank correspondant.

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

Les valeurs de cas d'une énumération sont des valeurs réelles, et non simplement une autre façon d'écrire leurs valeurs brutes. En fait, dans les cas où il n’existe pas de valeur brute significative, vous n’êtes pas obligé d’en fournir une.

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.

Notez les deux manières dont le cas Hearts de l'énumération est mentionné ci-dessus : Lors de l'attribution d'une valeur à la constante hearts , le cas d'énumération Suit.Hearts est désigné par son nom complet car la constante n'a pas de type explicite spécifié. À l'intérieur du commutateur, le cas d'énumération est désigné par la forme abrégée .Hearts car la valeur de self est déjà connue pour être un costume. Vous pouvez utiliser la forme abrégée chaque fois que le type de la valeur est déjà connu.

Si une énumération comporte des valeurs brutes, ces valeurs sont déterminées dans le cadre de la déclaration, ce qui signifie que chaque instance d'un cas d'énumération particulier a toujours la même valeur brute. Un autre choix pour les cas d'énumération consiste à associer des valeurs au cas : ces valeurs sont déterminées lorsque vous créez l'instance et elles peuvent être différentes pour chaque instance d'un cas d'énumération. Vous pouvez considérer les valeurs associées comme se comportant comme des propriétés stockées de l’instance de cas d’énumération.

Par exemple, considérons le cas de la demande des heures de lever et de coucher du soleil à un serveur. Le serveur répond avec les informations demandées, soit il répond par une description de ce qui n'a pas fonctionné.

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.

Remarquez comment le lever du soleil et le coucher du soleil sont extraits de la valeur ServerResponse dans le cadre de la correspondance de la valeur par rapport aux cas de commutateur.

Utilisez struct pour créer une structure. Les structures soutiennent bon nombre des mêmes comportements que les classes, y compris les méthodes et les initialiseurs. L'une des différences les plus importantes entre les structures et les classes est que les structures sont toujours copiées lorsqu'elles sont passées dans votre code, mais les classes sont transmises par référence.

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.

Protocoles et extensions

Utilisez protocol pour déclarer un protocole.

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

Les classes, les énumérations et les structures peuvent tous adopter des protocoles.

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?

Remarquez l'utilisation du mot-clé mutating dans la déclaration de SimpleStructure pour marquer une méthode qui modifie la structure. La déclaration de SimpleClass n'a besoin d'aucune de ses méthodes marquée comme mutant car les méthodes d'une classe peuvent toujours modifier la classe.

Utilisez extension pour ajouter des fonctionnalités à un type existant, telles que de nouvelles méthodes et des propriétés calculées. Vous pouvez utiliser une extension pour ajouter une conformité au protocole à un type qui est déclaré ailleurs, ou même à un type que vous avez importé d'une bibliothèque ou d'un cadre.

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.

Vous pouvez utiliser un nom de protocole comme n'importe quel autre type nommé - par exemple, pour créer une collection d'objets qui ont des types différents mais qui se conforment à un seul protocole. Lorsque vous travaillez avec des valeurs dont le type est un type de protocole, les méthodes en dehors de la définition du protocole ne sont pas disponibles.

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

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

Même si le protocolValue variable a un type d'exécution de SimpleClass , le compilateur le traite comme le type donné d' ExampleProtocol . Cela signifie que vous ne pouvez pas accéder à des méthodes ou des propriétés accidentelles que la classe implémente en plus de sa conformité protocole.

La gestion des erreurs

Vous représentez des erreurs en utilisant tout type qui adopte le protocole Error .

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

Utilisez throw pour lancer une erreur et throws pour marquer une fonction qui peut lancer une erreur. Si vous lancez une erreur dans une fonction, la fonction renvoie immédiatement et le code qui appelé la fonction gère l'erreur.

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

Il existe plusieurs façons de gérer les erreurs. Une façon consiste à utiliser do-catch . À l'intérieur du bloc do , vous marquez le code qui peut lancer une erreur en écrivant d'essayer devant lui. À l'intérieur du bloc catch , l'erreur est automatiquement donnée à l' error de nom à moins que vous ne lui donnez un nom différent.

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.

Vous pouvez fournir plusieurs blocs catch qui gèrent des erreurs spécifiques. Vous écrivez un motif après catch comme vous le faites après case dans un interrupteur.

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?

Une autre façon de gérer les erreurs est d'utiliser try? Pour convertir le résultat en facultatif. Si la fonction lance une erreur, l'erreur spécifique est rejetée et le résultat est nil . Sinon, le résultat est un contenant facultatif la valeur renvoyée par la fonction.

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

Utilisez defer pour écrire un bloc de code qui est exécuté après tous les autres code de la fonction, juste avant le retour de la fonction. Le code est exécuté, que la fonction lance une erreur. Vous pouvez utiliser defer pour écrire le code de configuration et de nettoyage les uns à côté des autres, même s'ils doivent être exécutés à différents moments.

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

Génériques

Écrivez un nom à l'intérieur des supports d'angle pour faire une fonction ou un type générique.

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"]

Vous pouvez faire des formes génériques de fonctions et de méthodes, ainsi que des classes, des énumérations et des structures.

// 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)

Utilisez where après le nom de type pour spécifier une liste des exigences - par exemple, pour exiger que le type implémente un protocole, pour exiger que deux types soient les mêmes, ou pour exiger qu'une classe ait une superclasse particulière.

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

L'écriture <T: Equatable> est la même que l'écriture <T> ... where T: Equatable .