Letní akce! Lákají tě IT školení C#, Javy a PHP v Brně? Přihlas se a napiš nám do zpráv kód "BRNO 500" pro slevu 500 Kč na libovolný brněnský kurz. Lze kombinovat se slevami uvedenými u školení i použít pro více kurzů. Akce končí 28.7.

Lekce 10 - Vlastnosti ve Swift

Swift OOP Vlastnosti ve Swift

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Statika ve Swift, jsme si vysvětlili statiku. V dnešním Swift tutoriálu se podíváme na slíbené deklarování pokročilejších vlastností.

Gettery a settery

Velmi často se nám stává, že chceme mít kontrolu nad změnami nějaké vlastnosti objektu zvenčí. Budeme chtít vlastnost nastavit jako read-only nebo reagovat na její změny. Založme si nový projekt (název Vlastnosti) a vytvořme následující třídu Student, která bude reprezentovat studenta v nějakém informačním systému.

class Student: CustomStringConvertible {
        var jmeno: String
        var muz: Bool
        var vek: Int
        var plnolety: Bool

        init(jmeno: String, pohlavi: Bool, vek: Int) {
                self.jmeno = jmeno
                self.muz = pohlavi
                self.vek = vek
                plnolety = true
                if vek < 18 {
                        plnolety = false
                }
        }

        var description: String {
                var jsemPlnolety = "jsem"
                if (!plnolety) {
                        jsemPlnolety = "nejsem"
                }
                var pohlavi = "muž"
                if (!muz) {
                        pohlavi = "žena"
                }
                return "Jsem \(jmeno), \(pohlavi). Je mi \(vek) let a \(jsemPlnolety) plnoletý."
        }

}

Třída je velmi jednoduchá, student se nějak jmenuje, je nějakého pohlaví a má určitý věk. Podle tohoto věku se nastavuje vlastnost plnolety pro pohodlnější vyhodnocování plnoletosti na různých místech systému. K uložení pohlaví používáme hodnotu Bool, zda je student muž. Konstruktor dle věku určí, zda je student plnoletý. Vlastnost description je navržena pro potřeby tutoriálu tak, aby nám vypsala všechny informace. V reálu by vrátila pravděpodobně jen jméno studenta. Pomocí konstruktoru si nějakého studenta vytvořme:

let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20)
print(s)

Výstup:

Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.

Vše vypadá hezky, ale vlastnosti jsou přístupné jak ke čtení, tak k zápisu. Objekt tedy můžeme rozbít například takto (hovoříme o nekonzistentním vnitřním stavu):

let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20)
s.vek = 15
s.muz = false
print(s)

Výstup:

Jsem Pavel Hora, žena. Je mi 15 let a jsem plnoletý.

Určitě musíme ošetřit, aby se plnoletost obnovila při změně věku. Když se zamyslíme nad ostatními vlastnostmi, není nejmenší důvod, abychom je taktéž umožňovali modifikovat. Student si za normálních okolností asi jen stěží změní pohlaví nebo jméno. Bylo by však zároveň vhodné je vystavit ke čtení, nemůžeme je tedy pouze pouze nastavit jako private. V dřívějších lekcích Swift kurzu jsme k tomuto účelu používali metody, které sloužily ke čtení privátních vlastností. Jejich název jsme volili jako vratVek() a podobně. Ke čtení vybraných vlastností vytvoříme také metody a vlastnosti označíme jako privátní, aby se nedaly modifikovat zvenčí. Třída by nově vypadala např. takto (vynechal jsem konstruktor a description):

class Student {
        var jmeno: String
        var muz: Bool
        var vek: Int
        var plnolety: Bool

        // ...

        func vratJmeno() -> String {
                return jmeno
        }

        func vratPlnoletost() -> Bool {
                return plnolety
        }

        func vratVek() -> Int {
                return vek
        }

        func jeMuz() -> Bool {
                return muz
        }

        func nastavVek(hodnota: Int) {
                vek = hodnota
                // přehodnocení plnoletosti
                plnolety = true
                if vek < 18 {
                        plnolety = false
                }
        }

}

Metody, co hodnoty jen vracejí, jsou velmi jednoduché. Nastavení věku má již nějakou vnitřní logiku, při jeho změně musíme totiž přehodnotit vlastnost plnolety. Zajistili jsme, že se do proměnných nedá zapisovat jinak, než my chceme. Máme tedy pod kontrolou všechny změny vlastností a dokážeme na ně reagovat. Nemůže se stát, že by nám někdo vnitřní stav nekontrolovaně měnil a rozbil.

Metodám k navrácení hodnoty se říká gettery a metodám pro zápis settery. Pro editaci ostatních vlastností bychom udělali např. jednu metodu editujStudenta(), která by byla podobná konstruktoru. Jméno, věk a podobně by se tedy měnily pomocí této metody, tam bychom mohli např. kontrolovat, zda hodnoty dávají smysl, opět bychom odchytili všechny pokusy o změnu na jediném místě.

Ptaní se na vlastnosti pomocí metod je ovšem pracné a může být i matoucí. Swift proto obsahuje pro vlastnosti další syntaxi.

private(set)

Pokud chceme pouze zamezit tomu, aby bylo vlastnosti možné nastavovat zvenčí, pomůže nám modifikátor přístupu private(set). Ten nyní umístíme před vlastnosti naší třídy, zachováme tím perfektní zapouzdření a gettery (metody) pro tyto vlastnosti již nebudeme potřebovat, proto je odstraníme.

private(set) var jmeno: String
private(set) var muz: Bool
private(set) var vek: Int

Uvnitř třídy se vlastnosti chovají standardně, ale zvenčí je nemůžeme měnit.

Computed properties

Elegantně jsme zamezili nechtěným modifikacím vlastností naší třídy zvenčí. Jinou technikou můžeme docílit také toho, že se vlastnost chová jako metoda, ale když se na ní ptáme, nepíšeme za jejím názvem závorku (). To se hodí pro vlastnosti, které vracejí hodnotu na základě jiných vlastností. Takovýmto vlastnostem ve Swift říkáme Computed properties. V naší třídě je využijeme pro plnoletost, kde místo uložené Bool hodnoty vrátíme přímo výraz vek < 18. Tím bude hodnota vlastnosti vždy aktuální. Podobně funguje i vlastnost description, kterou jsme doteď používali spíše intuitivně. Vzpomeňte si, že vracela vždy aktuální textovou podobnu instance na základě dalších vlastností. Upravme tedy vlastnost plnolety:

var plnolety: Bool {
        return vek >= 18
}

Takto tedy bude vypadat třída po všech úpravách výše:

class Student: CustomStringConvertible {

        private(set) var jmeno: String
        private(set) var muz: Bool
        private(set) var vek: Int
        var plnolety: Bool {
                return vek >= 18
        }

        init(jmeno: String, pohlavi: Bool, vek: Int) {
                self.jmeno = jmeno
                self.muz = pohlavi
                self.vek = vek
        }

        var description: String {
                var jsemPlnolety = "jsem"
                if (!plnolety) {
                        jsemPlnolety = "nejsem"
                }
                var pohlavi = "muž"
                if (!muz) {
                        pohlavi = "žena"
                }
                return "Jsem \(jmeno), \(pohlavi). Je mi \(vek) let a \(jsemPlnolety) plnoletý."
        }
}

Tímto jsme napsali nejjednodušší getter, který zvenčí vypadá jako obyčejná vlastnost, ale lze pouze číst. Lepší název proměnné by byl jePlnoletý, aby odpovídal konvencím pro Bool hodnoty, ale my řešíme vlastnosti obecně :-)

Samozřejmě, pokud byste v getteru měli náročnější výpočet a přistupovali k vlastnosti často, tak se vyplatí zamyslet nad optimalizací a výsledek počítat při nastavení vlastností, z kterých vychází. V našem případě je to vlastnost vek.

Vypadalo by to např. takto:

private var _vek: Int
private(set) var plnolety: Bool

var vek : Int {
        get {
                return _vek
        }
        set {
                plnolety = newValue >= 18
                _vek = newValue
        }
}

V tomto případě si musíme vytvořit ještě proměnnou _vek, která bude držet samotný věk a při jeho nastavení skrze vek také vypočítáme, jestli je student plnoletý. Tato forma se ale prakticky nepoužívá a tyto settery a gettery slouží vyloženě pro computed properties. newValue, jak jste asi poznali, zde reprezentuje právě nově přiřazovanou hodnotu.

Sledování změn vlastností

Často je dobré umět reagovat, když dojde ke změně nějaké vlastnosti. Např. chceme přepočítat další vlastnosti, spustit metodu, pokud byla dosažena určitá hranice nebo např. aktualizovat uživatelské rozhraní.

Swift nám umožňuje na přiřazení nové hodnoty snadno reagovat a možnost se jmenuje property observers. Máme k dispozici klíčová slova willSet a didSet. Ta se píší do složených závorek jako v případě getterů a setterů a jejich názvy prozrazují, co dělají. willSet bude zavoláno těsně před nastavením nové hodnoty do vlastnosti a didSet po jejím nastavení. Prakticky asi nikdy nepoužijete oba zároveň a didSet se používá častěji.

Můžeme si to opět ukázat na příkladu s plnoletostí.

private(set) var plnolety: Bool

private(set) var vek : Int {
        didSet {
                plnolety = vek >= 18
        }
}

Jakmile dojde k nastavení věku, přepočítá se i plnoletost. V tomto případě tradiční settery a gettery skutečně nebyly nejlepší volba. Zde záleží na vás, jestli se vám více líbí didSet řešení nebo vlastnost plnolety s jednoduchým getterem pouze pro čtení. U didSet a willSet mějte pouze na paměti, že se bohužel nezavolají, pokud je hodnota nastavena z konstruktoru, tudíž v našem případě musíme v konstruktoru studenta plnoletost poprvé nastavit sami.

Nastavování věku a plnoletosti si můžeme ještě otestovat, přidáme si jednoduchou metodu:

func nastavVek(_ vek: Int) {
        self.vek = vek
}

A upravíme main.swift:

let s = Student(jmeno: "Pavel Hora", pohlavi: true, vek: 20)
print(s)
s.nastavVek(17)
print(s)

Výstup:

Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.
Jsem Pavel Hora, muž. Je mi 17 let a nejsem plnoletý.

Vlastnosti budeme odteď používat stále, umožňují nám totiž objekty dokonale zapouzdřit. Pro Swift jsou prakticky všechny proměnné tříd vlastnosti, poté už záleží, jak jsou konkrétně implementované. Nezapomeňte na užitečný a jednoduchý modifikátor private(set) :-)

V příští lekci, Protokoly (rozhraní) ve Swift, se podíváme jak se ve Swift pracuje s datem a časem.


 

 

Článek pro vás napsal Filip Němeček
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Autor se příležitostně věnuje vývoji iOS aplikací či těch webových za pomocí frameworku Django. Aktuální projekt: hrejzdarma.cz Twitter: @nemecek_f (neprogramátorské tweety)
Miniatura
Předchozí článek
Statika ve Swift
Miniatura
Následující článek
Protokoly (rozhraní) ve Swift
Aktivity (4)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!