Lekce 1 - Úvod do kolekcí a genericita ve Swift
Vítejte v online kurzu zaměřeném na různé typy kolekcí programovacího jazyka Swift.
Kolekce
Pojem kolekce označuje soubor dat, které jsou většinou stejného typu a
slouží ke specifickému účelu. Během předchozích Swift
kurzů jsme se již setkali s polem (Array
) jako základní
kolekcí. Swift výběr kolekcí velmi zjednodušuje, protože nabízí pouze
hlavní tři. Základní pole již známe, později si ukážeme pokročilejší
metody a seznámíme se se zbylými kolekcemi Dictionary
(slovník)
a Set
(množina).
Generické a obecné kolekce
Když se zamyslíme nad tím, jak bychom si udělali vlastní kolekci, jistě
bychom po nějaké době dospěli k problému. Byl by jím datový typ kolekce.
Chtěli bychom si např. naprogramovat vlastní Array
, vytvořili
bychom třídu MojeArray.swift
, do ní přidali příslušné
metody a vše potřebné. Protože však chceme, aby byla naše kolekce
univerzální a uměla tedy ukládat např. jak Inty, tak uživatele, bude
problém s datovým typem prvků uvnitř kolekce. Existují 2 varianty, jak
tento problém vyřešit, prakticky se ale používá pouze jedna. Ta lepší
samozřejmě
Obecné kolekce
Ve Swiftu můžeme libovolný datový typ přiřadit do typu
Any
. Ten bychom v obecné kolekci mohli použít a do kolekce poté
uložit v podstatě cokoli. Nevýhodou je, že sama kolekce skutečný datový
typ prvků nezná a proto umí prvky navracet jen jako obecné objekty. Po
získání prvku z kolekce bychom museli řešit přetypování.
Generické kolekce
Generické kolekce řeší problém s datovým typem na úrovni jazyka Swift.
Zavádí tzv. genericitu. Zjednodušeně řečeno se jedná o možnost
specifikovat datový typ až ve chvíli vytvoření instance. Ve třídě
samotné kolekce se poté pracuje s generickým typem, který slouží jako
zástupce pro budoucí datový typ. Můžeme si to představit tak, že se
generický typ ve třídě změní např. na String
ve chvíli,
když vytvoříme její instanci. Jedná se tedy o možnost třídy nějakým
způsobem parametrizovat.
Generické Array
již známe. Onen datový typ (parametr) se
generickým třídám specifikuje ve špičatých závorkách. Máme možnost
specifikovat datový typ pouze jednou, při vytvoření kolekce. Jakékoli
další přetypování odpadá. Ačkoli jsme používali k vytvoření polí
zjednodušenou syntaxi, bylo by jej možné vytvořit i takto:
var pole = Array<String>() pole.append("položka") let polozka = pole[0]
Zápis [String]()
, který jsme byli zvyklí používat doposud,
by fungoval úplně stejně.
Genericita
Genericita je samozřejmě vlastnost jazyka Swift a my ji máme možnost ve svých třídách používat.
Zatím se nebudeme zatěžovat tvorbou vlastní kolekce. Vytvořme si
třídu, která bude jednoduše spravovat jednu proměnnou. Proměnná bude
generická, tedy libovolného datového typu. Založte si nový projekt,
konzolovou aplikaci s názvem Genericita
. Přidejte si novou
třídu, pojmenujme ji nyní pro studijní účely pouze Trida
. V
její deklaraci přidáme generický parametr, který pojmenujeme
T
:
class Trida<T> {
}
Generických parametrů můžeme zadat ve špičatých závorkách více, oddělíme je čárkou. Někdy se to může hodit, my se s tím setkáme dále u generických slovníků.
Přesuneme se do souboru main.swift
, kde si vytvoříme instanci
naší třídy:
let instance = Trida<Int>()
Nezapomeneme na špičaté závorky jak u datového typu, tak u konstruktoru.
Nyní jsme parametru T
v této instanci třídy určili datový typ
Int
. Stejně tak si můžeme udělat další instanci té samé
třídy a parametru T
dát úplně jiný datový typ, např.
String
. Stačí nám tedy 1 třída pro více datových typů.
Pokračujme a vytvořme si ve třídě vlastnost. T
můžeme
použít jako běžný datový typ:
private var promenna: T
Třídě ještě dodáme konstruktor, který proměnnou inicializuje.
init(promenna: T) { self.promenna = promenna }
V main.swift
aktualizujeme vytvoření instance:
let instance = Trida<Int>(promenna: 10)
Nyní instance obsahuje vlastnost promenna
, která je typu
Int
a nabývá hodnoty 10.
Můžeme dokonce přidat metodu, která bude mít navíc další generický parametr (jiný, než má třída). Mohla by vypadat např. takto:
func porovnej<T2>(a: T2) -> Bool { return type(of: promenna) == type(of: a) }
Metoda porovnává datový typ vlastnosti promenna
s datovým
typem parametru. Mohla by s proměnnými ale samozřejmě dělat cokoli jiného.
Zkusíme si tedy porovnat náš Int
s nějakým jiným typem:
instance.porovnej(a: "text")
Další konstrukce
Pro úplnost si ještě uveďme několik konstrukcí.
Generický parametr třídy je možné blíže specifikovat, přesněji
omezit. Slouží k tomu dvojtečka přímo za generickým parametrem. Můžeme
tak nastavit, že udaný datový typ musí např. obsahovat protokol
Equatable
(aby fungoval ==
operátor):
class Trida<T: Equatable> { // ... }
Díky tomu můžeme na proměnných typu T
nyní uvnitř třídy
volat metody z daného rozhraní. Samotné rozhraní může opět obsahovat
generický parametr, abychom generické typy mohli používat i v hlavičkách
jeho metod.
Můžeme také použít zápis pomocí klíčové slova
where
:
class Trida<T> where T:Equatable { // ... }
Záleží, co se vám líbí více. Pro přehlednost bych doporučil
where
používat, když máte více specifikací.
Pokud chceme specifikovat více protokolů, tak je v první variantě zápisu
oddělíme pomocí &
. Při where
stačí použít
čárku a zopakovat pro jaký parametr specifikaci uvádíme.
class Trida<T: Equatable & Comparable> { // ... }
class Trida<T> where T: Equatable, T: Comparable { // ... }
V příští lekci, Filtrování a mapování polí ve Swift, se znovu podíváme na pole a vysvětlíme si pokročilé metody. Naučíte se pole řadit a filtrovat podle vlastních parametrů.