NEW: Join my free 100 Days of SwiftUI challenge today! >>

Opționale

Swift este un limbaj de programare foarte sigur, ceea ce înseamnă că încearcă din greu să se asigure că orice vei scrie nu va crăpa în moduri surprinzătoare.

Unul dintre cele mai des întâlnite moduri ale unui cod de a crăpa este atunci când încearcă să folosească date greșite sau lipsă.

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

Această funcție nu acceptă niciun parametru și returnează un string: "Hate". Dar dacă azi este o zi însorită, și acei hateri nu prea au chef de a urî – atunci ce? Ei bine, poate nu vrem să returnăm nimic: acest hater nu urăște pe nimeni azi.

Acum, când vine vorba de string-uri ai putea crede că un string gol este o bună cale de a nu spune nimic, și asta s-ar putea să fie adevărat uneori. Dar cum rămâne cu numerele – este 0 un "număr gol"? Sau -1?

Înainte să începi sa creezi reguli imaginare, Swift are o soluție: opționale. O valoare opțională este o valoare care poate sau nu să aibă o valoare. Mulți oameni găsesc opționalele greu de înțeles, și asta este în regulă – O să încerc să le explic în mai multe feluri cu speranța că unul va avea sens.

Pentru moment, imaginează-ți un chestionar unde întrebi pe cineva, "Pe o scară de la 1 la 5 cât de extraordinară este Taylor Swift?" – ce ar răspunde cineva care nu a auzit niciodată de ea? 1 ar fi prea puțin și 5 ar fi prea mult când ei habar nu aveau cine este Taylor Swift. Soluția este folosirea opționalelor: "Nu vreau să dau niciun număr".

Folosirea -> String înseamnă că "vom returna cu siguranță un string", ceea ce înseamnă că această funcție nu poate returna o valoare, și prin urmare poate fi apelată în siguranță știind că mereu vei obține o valoare de tip string. Dacă vrem să îi transmitem lui Swift că această funcție ar putea sau nu returna o valoare, trebuie să folosim asta în schimb:

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

Observă semnul întrebării: String? înseamnă "string opțional". În cazul nostru încă returnăm "Hate" indiferent de circumstanțe, dar haide să modificăm mai departe acea funcție: dacă este însorit, haterii s-au schimbat și au renunțat la viața lor plină de ură, așa că nu vom vrea să returneze nicio valoare. În Swift, această "nicio valoare" are un nume special: nil.

Schimbă funcția în felul acesta:

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

Aceasta acceptă un parametru de tip string (vremea) și returnează un string (gradul de ură), dar acea valoare poate să fie acolo sau poate să nu – este nil. În acest caz, înseamnă că putem obține un string, sau putem obține nil.

Acum partea importantă: Swift vrea ca codul tău să fie foarte sigur, și încercarea de a folosi o valoare nil (nulă) este o idee rea. Ar putea să îți facă codul să crape, ar putea să îți încurce logica, sau ar putea să afișeze ceva greșit în interfață. Drept urmare, când declari o valoare ca fiind optională, Swift se va asigura că o folosești în siguranță.

Să încercăm asta acum: adaugă aceste linii de cod în playground-ul tău:

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

Prima linie de cod creează o variabilă de tip string, și a doua linie îi atribuie acesteia valoarea funcției getHaterStatus() – Și astăzi plouă, deci haterii vor urî cu siguranță.

Acel cod nu va rula, pentru că am spus că status este de tipul String, ceea ce necesită o valoare, dar getHaterStatus() poate să nu returneze niciuna deoarece returnează un string opțional. Noi am spus că în variabila status va fi cu siguranță o valoare, dar getHaterStatus() poate să nu returneze nimic. Swift pur și simplu nu te va lăsa să faci această greșeală, ceea ce este extrem de bine deoarece astfel oprește o întreagă mulțime de bug-uri.

Pentru a corecta problema, trebuie să facem variabila status de tipul String?, sau să înlăturăm cu totul tipul și să-l lăsăm pe Swift să deducă tipul. Prima variantă arată în felul următor:

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

Iar a doua arată așa:

var status = getHaterStatus(weather: "rainy")

Indiferent de varianta pe care o alegi, valoarea aceea s-ar putea să fie acolo sau s-ar putea să nu fie, și în mod implicit Swift nu te va lăsa să o folosești într-un mod periculos. Drept exemplu, imaginează-ți o funcție ca aceasta:

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

Ea primește un string și afișează un mesaj în funcție de conținutul acestuia. Această funcție primește o valoare String, și nu o valoare String? – nu poți trimite un opțional aici, vrea un string adevărat, ceea ce înseamnă că nu o putem apela folosind variabila status.

Swift are două soluții. Ambele sunt folosite, dar una dintre ele este categoric preferată. Prima soluție se numeste desfacerea opționalului (optional unwrapping), și se face în interiorul unei declarații condiționate folosind o sintaxă specială. Face două lucruri în același timp: verifică dacă opționalul are o valoare, și dacă are atunci îl desface într-un tip care nu este opțional apoi rulează codul.

Așa arată sintaxa:

if let unwrappedStatus = status {
    // unwrappedStatus contains a non-optional value!
} else {
    // in case you want an else block, here you go…
}

Aceste declarații if let verifică și desfac într-o linie de cod, făcându-le foarte populare. Folosind această metodă, putem desface în siguranță valoarea returnată de getHaterStatus() și ne putem asigura că apelăm takeHaterAction() cu un string care nu este opțional. Aici este codul 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)
}

Dacă înțelegi acest concept, ești bine-venit să sari la titlul "Desfacere forțată a opționalelor". Dacă nu ești încă foarte sigur cu privire la opționali, continuă să citești.

OK, dacă ești încă aici înseamnă că explicațiile de mai sus nu au niciun sens, sau ai cam înțeles însă ți-ar prinde bine niște clarificări. Opționalele sunt folosite extensiv în Swift, prin urmare chiar trebuie să le înțelegi. O să încerc să ți le explic, într-un mod diferit, și sper că ast va ajuta!

Uite o funcție nouă:

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
}

Ea primește numele albumului lui Taylor Swift, și returnează anul în care a fost lansat. Dar dacă apelăm funcția cu numele "Lantern" pentru că am încurcat Taylor Swift cu Hudson Mohawke (o greșeală ușor de facut, corect?) atunci va returna 0 deoarece nu este unul din albumele lui Taylor Swift.

Dar are sens 0 aici? Desigur, dacă albumul ar fi fost lansat în anul 0 d.Hr când Caesar Augustus era împăratul Romei, 0 ar putea avea sens, dar aici creează confuzii – oamenii trebuie să știe dinainte că 0 înseamnă "necunoscut".

O idee mult mai bună este de a rescrie acea funcție în așa fel încât ea să returneze un întreg (când un an a fost găsit) sau nil (când nimic nu a fost gasit), ceea ce este ușor mulțumită opționalelor. Așa arată noua funcție:

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
}

Din moment ce returnează nil va trebui să o desfacem folosind if let deoarece trebuie să verificăm dacă valoarea există sau nu.

Dacă acum înțelegi conceptul, ești bine-venit să sari la titlul "Desfacerea forțată a opționalelor". Dacă încă nu ești chiar sigur, continuă să citești.

OK, dacă ești încă aici înseamnă că te chinui cu opționalele, de aceea am să încerc să îți mai explic odată.

Avem un șir de nume:

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

Dacă am vrea să scriem o funcție care caută în șir un anumit nume și apo returnează indexul acestuia, am putea scrie ceva de genul acesta:

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

    return 0
}

Aceasta parcurge toate elementele din șir, returnează poziția atunci când condiția este îndeplinită, altfel returnează 0.

Acum încearcă să rulezi aceste patru linii de cod:

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)

Asta va produce rezultatul 0, 1, 2, 0 – poziția lui James și Bob este aceeași, chiar dacă unul există și altul nu. Asta din cauză că am folosit 0 pentru "nu a fost găsit". Am putea repara ușor scriind -1 în loc de 0, dar indiferent dacă este 0 sau -1 tot vom avea o problemă din cauză că va trebui să ne amintim că acel număr înseamnă "nu a fost găsit".

Soluția sunt opționalele: Returnează un întreg dacă ai găsit valoarea potrivită, altfel returnează nil. De fapt, aceasta este abordarea folosită și de metoda "find in array": someArray.index(of: someValue).

Când lucrezi cu aceste valori care "ar putea fi, sau ar putea să nu fie", Swift te forțează să le desfaci înainte să le folosești, astfel luând la cunoștință că acolo se poate să nu fie o valoare. Asta face sintaxa if let: dacă opționalul are o valoare atunci desfă-o și folosește-o, altfel nu o folosi deloc. Nu poți folosi o valoare care poate fi goală din greșeală, pentru că Swift nu te va lăsa.

Dacă încă nu ești sigur cum funcționează opționalele, atunci cel mai bun lucru ar fi să mă întrebi pe Twitter și voi încerca să te ajut: mă găsești astfel @twostraws.

Desfacerea forțată a opționalelor

Swift îți dă voie să suprascri metodele sale de siguranță folosind semnul exclamării: !. Dacă știi că un opțional are cu siguranță o valoare, atunci poți să îl desfaci forțat prin a pune semnul exclamării după el.

Ai grijă totuși: dacă încerci asta pe o variabilă care nu are o valoare, codul va crăpa.

Pentru a forma un exemplu funcțional, iată codul de bază:

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

Această funcție primește anul în care albumul a fost lansat. Dacă acesta nu poate fi găsit, year va fi nil, și un mesaj de eroare va fi printat. Altfel, anul va fi printat.

Sau nu? Ei bine, yearAlbumReleased() returnează un întreg opțional, iar acest cod nu folosește if let pentru a-l despacheta. Drept urmare, va afișa următoarele: "A fost lansat în Optional(2012)" – probabil nu ceea ce ne-am dorit!

În acest moment deja am verificat dacă valoarea este validă, deci este puțin lipsit de rost să mai avem un if let pentru a despacheta opționalul în mod sigur. Astfel, Swift ne oferă o soluție – modifică al doilea print() astfel:

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

Fii atent la semnul exclamării: înseamnă "Sunt sigur că exista o valoare, așa că desfă forțat."

Opționale desfăcute implicit

Poți folosi semnul exclamării pentru a crea opționale desfăcute implicit, și aici unii oameni încep să devină confuzi. Așa că, te rog citește cu atenție!

  • O variabilă obișnuită trebuie să conțină o valoare. De exemplu: String trebuie să conțină string, chiar dacă acel string este gol, ex:. "". Nu poate fi nil.
  • O variabilă opțională poate să conțină o valoare, sau poate să nu. Trebuie să fie desfăcută înainte de a fi folosită. De exemplu: String? poate să conțină un string, sau poate să conțină nil. Singura cale de a afla este de a-l desface.
  • Un opțional desfăcut implicit poate conține o valoare, sau poate să nu conțină nicio valoare. Dar nu trebuie să fie desfăcut înainte de a fi folosit. Swift nu va face verificarea în locul tău, prin urmare trebuie să fii foarte atent. De exemplu: String! poate conține un string, sau poate conține nil – și este datoria ta să-l folosești corespunzător. Este ca un opțional obișnuit, dar Swift îți dă voie să îi accesezi valoarea fără a-l desface în siguranță. Dacă încerci să o faci, înseamnă că știi că acolo se află o valoare – dar dacă greșești aplicația va crăpa.

În principiu vei avea de-a face cu opționale desfăcute implicit atunci când lucrezi cu elemente ale interfeței cu utilizatorul din UIKit în iOS sau AppKit în macOS. Ele trebuiesc declarate din prima, dar nu le poți folsi până nu au fost create – iar Apple preferă să creeze elementele de interfață cu userul în ultimul moment pentru a evita orice muncă dacă nu este necesară. Fiind forțat să desfaci mereu valori care știi cu siguranță că vor fi acolo este enervant, de aceea ele sunt desfăcute implicit.

Nu te teme dacă găsești opționalele desfăcute implicit greu de înțeles – va deveni mai clar pe măsură ce vei lucra cu limbajul.

SPONSORED Instabug helps you identify and resolve severe crashes quickly. You can retrace in-app events and know exactly which line of code caused the crash along with environment details, network logs, repro steps, and the session profiler. Ask more questions or keep users up-to-date with in-app replies straight from your dashboard. Instabug takes data privacy seriously, so no one sees your data but you! See more detailed features comparison and try Instabug's crash reporting SDK for free.

BUY OUR BOOKS
Buy Pro Swift 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 (Vapor Edition) 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 Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!