Lekce 6 - Typový systém: Optionals ve Swift
V předešlém cvičení, Řešené úlohy k 5. lekci Swift, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Nyní si konečně vysvětlíme koncept tzv. Optionals, který je ve Swift velmi důležitý. Řekneme si, co znamenají všechny ty vykřičníky ve zdrojovém kódu. S několika jsme se už setkali a byť se může pro začátek jednat o složitější koncept, tak bude lepší, když budete alespoň tušit, o co se jedná.
Koncept hodnoty nil
Programovací jazyky se musí nějak vypořádat se situací, kdy proměnná
nemá žádnou hodnotu. S takovým problémem se často setkáváme u funkcí,
které se nemusí provést korektně. Pokud se např. nepodaří načtení
čísla z konzole, nemělo by být vráceno žádné číslo, ale "prázdno".
Pokud by nám Swift vrátil v tomto případě např. hodnotu 0
nebo -1
, nepoznali bychom zda se číslo nepodařilo načíst nebo
zda uživatel vložil právě 0
nebo -1
. Za tímto
účelem se vymyslela speciální hodnota nil
, která bezpečně
označí, že je proměnná prázdná. V ostatních programovacích jazycích se
tato hodnota často jmenuje null
a funguje úplně stejně.
Pokud si ve Swift vytvoříme standardní proměnnou, prázdnou hodnotu
nil
do ní přiřadit nelze:
// Tento kód je chybný var cislo = 15 cislo = nil // Tento řádek vyvolá chybu
Někdo by ji tam totiž nemusel očekávat. Abychom nil
mohli do
proměnné přiřadit, musíme proměnnou označil jako
Optional
.
Optionals
Typ Optional
můžeme chápat jako jakýsi box, který slouží
k zabalení obyčejné proměnné. Box vždy existuje, po jeho
otevření ale hodnotu buď najdeme nebo je prázdný.
Otazník
Optional
typ vytvoříme tak, že za název datového typu
proměnné umístíme otazník ?
. Zkusme si
to:
var moznaCislo: Int? = 15 moznaCislo = nil
Kód se již přeložil v pořádku a proměnná moznaCislo
je
nyní prázdná, i když se jedná o číslo. To zní zatím dobře, že?
Je tu ovšem problém, který mnoho ostatních programovacích jazyků
nedokáže vyřešit. S moznaCislo
by nám nyní nemělo
být umožněno pracovat jako s obyčejnou proměnnou. Pokud bychom
napsali:
var moznaCislo: Int? = 15 moznaCislo = nil print(moznaCislo * 2)
a program se přeložil, mohl by za běhu spadnout v případě, že by
moznaCislo
bylo prázdné. Nemůžeme přeci vynásobit "nezadáno"
dvěma. Když si takový program zkusíte napsat, zjistíte, že nejde
přeložit. Podobně by nám Swift vynadal i při přístupu k
vlastnosti nebo metodě Optional
typu. Můžete si zkusit, že
kvůli výpisu délky druhého řetězce nepůjde následující kód
přeložit:
var s1 = "Ahoj" var s2: String? = "Světe" print(s1.count) print(s2.count)
Asi jste tušili, že Swift nepatří mezi jazyky, které by si tento problém neohlídaly
Null safety
Mechanismus, který již při překladu kontroluje jak Optional
typy používáme, se často nazývá null safety. Existuje
několik způsobů jak Optional
proměnnou použít. Postupně si
je vyzkoušíme.
Vykřičník
Optional můžeme vytvořit ještě druhým způsobem. Pomůže nám s tím
vykřičník !
:
var vzdyCislo : Int! // Můžeme napsat print(vzdyCislo + 1), ale aplikace spadne, protože v boxu vzdyCislo ještě číslo není
Tímto způsobem Swiftu říkáme, že se o proměnnou postaráme sami a
zajistíme, aby měla před přístupem k ní platnou hodnotu
(v tomto případě libovolné celé číslo). V kódu s ní můžeme
pracovat jako s obyčejným Int
, který jinak bez hodnoty
nejde deklarovat, ale snadno nám může program spadnout.
Určitě vám došlo, že význam vykřičníků v kódu je nějaké riziko a měli bychom se jim spíše vyhýbat.
Rozbalujeme box
Nyní si konečně vysvětlíme jak se k hodnotám dostat a proč jsme do našich kódu psali vykřičníky.
Operátor !
Začněme tím nejhloupějším, který jsme zatím v kurzu používali, aby
toho na nás nebylo ze začátku moc. Pomocí operátoru !
můžeme
Swift degradovat na starší jazyky jako je např. Java a
kontrolu null safety vypnout. Pokud v proměnné zrovna nebude
nil
, vše bude fungovat:
var moznaCislo: Int? = 15 print(moznaCislo! * 2)
Výsledek:
30
V anglické terminologii se pro tuto techniku používá výraz force unwrapping (vynutit rozbalení).
Pokud v proměnné ovšem prázdná hodnota bude, celá aplikace za běhu upadne s chybou.
var moznaCislo: Int? = 15 moznaCislo = nil print(moznaCislo! * 2)
Při překladu bychom na tuto chybu vůbec nepřišli. Tento způsob se využívá velmi zřídka a obecně v případech, kdy víme, že vždy bude hodnota nebo naopak její absence znamená, že nemá smysl kód dále vykonávat, protože jde o něco kritického.
Optional binding
K Optionals bychom měli vždy přistupovat opatrně právě skrze optional
binding. Používají se dvě hlavní konstrukce, jedna za pomoci tradiční
if
podmínky a druhá za pomoci slovíčka guard
.
Jedná se spíše o sémantické rozdíly (mají jiný význam). Nejdříve si
je ukážeme a následně vysvětlíme.
Ošetření Optional hodnoty podmínkou
var optionalNumber : Int? = 5 if let number = optionalNumber { print(number * 2) } else { print("Číslo není zadané") }
Kód v podmínce výše se spustí pouze v případě, že proměnná
optionalNumber
obsahuje hodnotu. Ta se následně uloží
do konstanty number
a v těle podmínky s ní pracujeme jako s
tradičním Int
, takže nejsme dále omezováni.
Pro zajímavost: Můžete používat také if var
,
ale prakticky se to nedělá, protože je daná proměnná stejně aktivní
pouze v tomto bloku a nedává smysl s ní moc manipulovat.
guard
Stručně se dá říci, že guard
funguje opačně a spustí
blok v případě, že podmínka neplatí. Používá se pouze ve funkcích,
které zatím neumíme, kde z funkce v bloku vystoupí pomocí příkazu
return
. Ukážeme si jej až dále v kurzu.
Rozbalení pomocí výchozí hodnoty
Ještě si ukážeme další možnost, jak se zbavit Optional
a
získat tradiční datový typ. Můžeme totiž jednoduše určit náhradní
hodnotu, která se použije, pokud je Optional
prázdný. Odborně
se označuje jako nil coalescing a disponuje vlastním
operátorem ??
.
var moznaCislo: Int? moznaCislo = nil let urciteCislo = moznaCislo ?? 4 print(urciteCislo)
Můžete vidět, že operátor ??
je binární, tudíž
vyžaduje hodnoty na obou stranách. Pokud levá strana není nil
(respektive prázdný Optional
), tak operátor dále nic neřeší
a přiřadí tuto hodnotu. Jestli je ale nil
, tak dojde k
přiřazení druhé hodnoty.
Zde se nám vypíše 4
, protože je proměnná
moznaCislo
nil
. Toto se hodí v případě, že lze
rozumně pokračovat také s výchozí hodnotou, kterou přes ??
nastavíme. Tento operátor je možné řetězit, ale pro přehlednost bych to
nedoporučoval.
V příští lekci, Pole ve Swift, se budeme věnovat polím.