UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Optionnels

Swift est un langage très sûr. Je veux dire par là que tout est mis en œuvre pour que votre code n’échoue jamais de manière surprenante.

L'une des façons la plus courante de faire échouer du code consiste à utiliser des données incorrectes ou manquantes. Par exemple, imaginez une fonction comme celle-ci :

func getHaterStatus() -> String {
    return "Hate"
}

Cette fonction n'accepte aucun paramètre et retourne une chaîne de caractères : "Hate". Mais que se passe-t-il si la journée est particulièrement ensoleillée et que ces personnes qui ont de la haîne (haters) ne se sentent pas détestées (hate) ? Eh bien, peut-être que nous ne voulons rien retourner : elles ne détestent personne aujourd'hui.

Maintenant, s’agissant d’une chaîne de caractères, vous pourriez penser qu’une chaîne vide est un excellent moyen de ne rien communiquer, ce qui peut parfois être vrai. Mais qu'en est-il des nombres, 0 est-il un "nombre vide" ? Ou -1 ?

Avant de commencer à essayer de créer des règles imaginaires pour vous-même, Swift propose une solution : les optionnels. Une valeur optionnelle ou facultative est une valeur qui peut avoir une valeur ou non. La plupart des gens ont du mal à comprendre les optionnels, et c’est normal - je vais essayer d’expliquer ce concept de plusieurs façons, en espérant que l’une d'entre elles fonctionnera.

Pour l’instant, imaginez un sondage dans lequel vous demandez à une personne : "Sur une échelle de 1 à 5, comment évaluez-vous le talent de Taylor Swift ?". Que répondrait quelqu'un qui n'a jamais entendu parler d'elle ? 1 serait injuste et 5 l'encenserait alors qu'il n'a aucune idée de qui est Taylor Swift. La solution consiste à utiliser les optionnels : "Je ne veux pas donner de numéro du tout."

Lorsque nous utilisons -> String, cela signifie "cette fonction retournera obligatoirement une chaîne de caractères", ce qui signifie qu'elle ne peut pas ne pas renvoyer de valeur, et peut donc être qualifiée de sûre, sachant qu'elle retournera toujours une valeur que nous pouvons utiliser comme une chaîne de caractères. Si nous voulons dire à Swift que cette fonction peut retourner une valeur ou non, nous devons utiliser cette syntaxe à la place :

func getHaterStatus() -> String? {
    return "Hate"
}

Remarquez le point d'interrogation supplémentaire : String? signifie "chaîne de caractères optionnelle". Maintenant, dans notre cas, nous renvoyons systématiquement "Hate", mais allons de l'avant et améliorons encore cette fonction : si le temps est ensoleillé (the weather is sunny), les personnes qui ont de la haîne (haters) ont tourné la page et ont abandonné leur vie de haine. Nous ne voulons donc rien retourner. En Swift, cette "absence de valeur" a un nom spécial : nil.

Modifiez la fonction comme ceci :

func getHaterStatus(weather: String) -> String? {
    if weather == "sunny" {
        return nil
    } else {
        return "Hate"
    }
}

Cette fonction accepte un paramètre de type chaîne de caractères (weather) et retourne une chaîne de caractères (l'état de la personne), mais cette valeur de retour peut être ou ne pas être présente (nil). Dans ce cas, cela signifie que nous pouvons obtenir soit une chaîne de caractères, soit nil.

Passons maintenant aux choses sérieuses : Swift veut que votre code soit vraiment sûr, et essayer d’utiliser une valeur nil est une mauvaise idée. Cela risquerait de faire crasher votre code, d'altérer sa logique ou de faire afficher des choses incorrectes dans votre interface utilisateur. En conséquence, lorsque vous déclarez une valeur comme étant optionnelle, Swift veille à la gérer en toute sécurité.

Essayons ceci maintenant : ajoutez ces lignes de code à votre playground :

var status: String
status = getHaterStatus(weather: "rainy")

La première ligne crée une variable de type chaîne de caractères et la seconde lui assigne la valeur getHaterStatus() - et aujourd'hui, comme le temps est pluvieux, les haîneux ont sans aucun doute la haîne.

Ce code ne fonctionnera pas, car nous avons dit que status est de type String, ce qui nécessite absolument une chaîne de caractères, mais getHaterStatus() peut ne pas en fournir une car elle retourne une chaîne de caractères optionnelle. C'est-à-dire que nous avons dit qu'il y aurait de façon certaine une chaîne de caractères dans status, mais getHaterStatus() pourrait ne rien retourner du tout. Swift ne vous laissera tout simplement pas commettre cette erreur, ce qui est extrêmement utile car il bloque ainsi efficacement toute un type de bugs courants.

Pour résoudre le problème, nous devons modifier le type de la variable status en String? ou simplement supprimer entièrement l'annotation du type de données et laisser Swift le déduire par lui-même. La première option ressemble à ceci :

var status: String?
status = getHaterStatus(weather: "rainy")

Et la seconde à cela :

var status = getHaterStatus(weather: "rainy")

Quelle que soit celle que vous choisissez, cette valeur peut être présente ou non, et par défaut, Swift ne vous laissera pas l’utiliser imprudemment. Par exemple, imaginez une fonction comme celle-ci :

func takeHaterAction(status: String) {
    if status == "Hate" {
        print("Hating")
    }
}

Cette fonction prend une chaîne de caractères et affiche un message en fonction de son contenu. Cette fonction prend une valeur de type String, et non une valeur de type String?- vous ne pouvez pas passer un optionnel ici, elle veut une vraie chaîne de caractères, ce qui signifie que nous ne pouvons pas l'appeler en utilisant la variable status.

Swift a deux solutions, mais l'une est certainement préférable à l'autre. La première s'appelle le déballage d'optionnel (optionnel unwrapping) et s'effectue dans une instruction conditionnelle à l'aide d'une syntaxe spéciale. Elle fait deux choses en même temps : vérifie si un optionnel a une valeur et, si tel est le cas, la déballe dans un type non optionnel, puis exécute un bloc de code.

La syntaxe ressemble à ceci :

if let unwrappedStatus = status {
    // unwrappedStatus contient une valeur non optionnelle !
} else {
    // au cas où vous souhaitez utiliser un bloc else, c'est ici…
}

Cette instruction if let vérifie et déballe un optionnel en une très courte ligne de code, ce qui la rend très répandue. En utilisant cette méthode, nous pouvons en toute sécurité déballer la valeur retournée par getHaterStatus() et nous assurer que nous appelons seulement takeHaterAction() avec une chaîne de caractères valide, non optionnelle. Voici le code complet :

func getHaterStatus(weather: String) -> String? {
    if weather == "sunny" {
        return nil
    } else {
        return "Hate"
    }
}

func takeHaterAction(status: String) {
    if status == "Hate" {
        print("Hating")
    }
}

if let haterStatus = getHaterStatus(weather: "rainy") {
    takeHaterAction(status: haterStatus)
}

Si vous avez compris ce concept, n'hésitez pas à passer au titre "Forcer le déballage des optionnels"`. Si vous n'êtes toujours pas à l'aise avec les optionnels, poursuivez votre lecture.

OK, si vous êtes toujours là, ça signifie que vous n'avez pas du tout compris l'explication ci-dessus, ou que vous n'avez pas tout compris et que vous avez besoin de quelques précisions. Les optionnels sont très utilisés en Swift, vous devez donc absolument les comprendre. Je vais essayer d'expliquer à nouveau, d'une manière différente, et j'espère que cela pourra vous aider !

Voici une nouvelle fonction :

func yearAlbumReleased(name: String) -> Int {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return 0
}

Elle prend le nom d’un album de Taylor Swift et retourne l’année de sa sortie. Mais si nous l'appelons avec le nom de l'album "Lantern" parce que nous avons confondu Taylor Swift avec Hudson Mohawke (une erreur facile à commettre, n'est-ce pas?), elle retourne 0 car ce n'est pas l'un des albums de Taylor.

Mais 0 a-t-il un sens ici ? Evidemment, si l'album était sorti en 0 après JC alors que César Auguste était empereur de Rome, 0 serait la réponse que nous attendons, mais ici c'est déroutant - les gens doivent savoir à l'avance que 0 signifie "non reconnu".

Une bien meilleure idée est de réécrire cette fonction afin qu’elle retourne un entier (quand une année est trouvée) ou nil (quand rien n’a été trouvé), ce qui est facile grâce aux optionnels. Voici la nouvelle fonction :

func yearAlbumReleased(name: String) -> Int? {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return nil
}

Maintenant qu'elle retourne nil, nous devons déballer le résultat en utilisant if let, car nous devons vérifier si une valeur existe ou non.

Si vous avez compris ce concept, n'hésitez pas à passer au titre "Forcer le déballage des optionnels"`. Si vous n'êtes toujours pas à l'aise avec les optionnels, poursuivez votre lecture.

OK, si vous êtes toujours là, ça signifie que vous avez vraiment des difficultés avec les optionnels, alors je vais tenter une dernière fois de les expliquer.

Voici un tableau de noms :

var items = ["James", "John", "Sally"]

Si nous voulions écrire une fonction qui regarde dans ce tableau et nous retourne l'indice d'un nom particulier, nous pourrions écrire quelque chose comme ceci :

func position(of string: String, in array: [String]) -> Int {
    for i in 0 ..< array.count {
        if array[i] == string {
            return i
        }
    }

    return 0
}

Cette fonction parcourt tous les éléments du tableau et retourne une position si elle trouve une correspondance, sinon elle retourne 0.

Maintenant, essayez de lancer ces quatre lignes de code :

let jamesPosition = position(of: "James", in: items)
let johnPosition = position(of: "John", in: items)
let sallyPosition = position(of: "Sally", in: items)
let bobPosition = position(of: "Bob", in: items)

Cela donne 0, 1, 2, 0 - les positions de James et Bob sont les mêmes, alors que l’un existe et que l’autre n’existe pas. C'est parce que j'ai utilisé 0 pour signifier "non trouvé". La solution de facilité pourrait être de retourner -1 pour non trouvé, mais que ce soit 0 ou -1, vous avez toujours un problème car vous devez vous rappeler que ce nombre spécifique signifie "non trouvé".

La solution à cela sont les optionnels : retourne un entier si tu as trouvé la correspondance, ou sinon nil. En fait, c'est exactement l'approche que les méthodes déjà intégrées "find in array" utilisent : someArray.index(of: someValue).

Lorsque vous travaillez avec ces valeurs "peut-être présent, ou pas", Swift vous oblige à les déballer avant de les utiliser, reconnaissant ainsi qu'il peut ne pas y avoir de valeur. C'est ce que fait la syntaxe if let : si l'optionnel possède une valeur, déballe-la et utilise-la, sinon ne l'utilise pas du tout. Vous ne pouvez pas utiliser une valeur éventuellement vide par accident, car Swift ne vous le permettra pas.

Si vous ne savez toujours pas comment fonctionnent les optionnels, la meilleure chose à faire est de me demander sur Twitter et j'essayerai de vous aider : vous pouvez me trouver [@twostraws] (http://twitter.com/twostraws ).

Forcer le déballage des optionnels

Swift vous permet d'outrepasser sa sécurité en utilisant un point d'exclamation : !. Si vous savez qu'un optionnel a forcément une valeur, vous pouvez forcer son déballage en plaçant ce point d'exclamation après celui-ci.

Soyez prudent s'il vous plaît. Car si vous essayez ceci sur une variable qui n'a pas de valeur, votre code va planter.

Pour vous montrer un exemple qui fonctionne, voici un code de base :

func yearAlbumReleased(name: String) -> Int? {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return nil
}

var year = yearAlbumReleased(name: "Red")

if year == nil {
    print("There was an error")
} else {
    print("It was released in \(year)")
}

Il permet d'obtenir l'année où un album est sorti. Si l'album ne peut pas être trouvé, nil est assigné à year et un message d'erreur est affiché. Sinon, c'est l'année qui est affichée.

Le fera-t-il vraiment ? Eh bien, yearAlbumReleased() retourne un entier optionnel, et ce code n'utilise pas if let pour déballer cet optionnel. En conséquence, il affichera le texte suivant : "It was released in Optional(2012)" - ce n’est probablement pas ce que nous voulons !

À ce stade, nous avons déjà vérifié dans le code que nous possédons une valeur valide. Il est donc inutile d’avoir un autre if let ici pour déballer l’optionnel en toute sécurité. Swift fournit donc une solution : remplacez le deuxième appel print() par ceci :

print("It was released in \(year!)")

Remarquez le point d'exclamation : cela signifie "Je suis certain que year contient une valeur, alors déballe-la de force immédiatement."

Optionnels implicitement déballés

Vous pouvez également utiliser cette syntaxe du point d’exclamation pour créer des optionnels implicitement déballés. C’est là que certaines personnes commencent vraiment à se perdre. Alors, s'il vous plaît lisez ceci attentivement !

  • Une variable habituelle doit contenir une valeur. Exemple : String doit contenir une chaîne de caractères, même si cette chaîne est vide, c'est-à-dire "". Elle ne peut pas être nulle (nil).
  • Une variable optionnelle peut contenir une valeur ou pas. Elle doit être déballée avant d’être utilisée. Exemple : String? peut contenir une chaîne de caractères ou nil. La seule façon de le savoir est de la déballer.
  • Un optionnel implicitement déballé peut contenir une valeur ou non. Mais il n'a pas besoin d'être déballé avant d'être utilisé. Swift ne vérifiera pas pour vous, vous devez donc faire très attention. Exemple : String! peut contenir une chaîne de caractères ou nil - et il vous appartient de l'utiliser correctement. C’est comme un optionnel normal, mais Swift vous permet d’accéder directement à la valeur sans sécurité. Si vous essayez de le faire, cela signifie que vous savez qu’il y a une valeur là, mais si vous vous trompez, votre application plantera.

Vous rencontrerez principalement des optionnels implicitement déballés lorsque vous travaillerez avec des éléments d'interface utilisateur dans UIKit sur iOS ou AppKit sur macOS. Ceux-ci doivent être déclarés à l’avance, mais vous ne pouvez pas les utiliser avant qu'ils aient été créés - et Apple aime créer les éléments d’interface utilisateur au dernier moment afin d’éviter tout travail inutile. Avoir à déballer continuellement des valeurs alors que vous savez pertinemment qu’elles contiennent de la donnée peut vite s'avérer particulièrement énervant, elles sont donc implicitement déballées.

Ne vous inquiétez pas si vous trouvez que les optionnels implicitement déballés sont un peu difficiles à comprendre - cela deviendra plus clair au fur et à mesure que vous travaillerez avec Swift.

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.