Co jsme si o Objective C ještě neřekli... - MujMAC.cz - Apple, Mac OS X, Apple iPod

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:

Seriály

Více seriálů



Začínáme s

Co jsme si o Objective C ještě neřekli...

14. května 2004, 00.00 | V minulém dílu jsme se seznámili se systémem objektů a ukázali jsme si všechny základní služby jazyka Objective C. Nyní se seznámíme se zbytkem konstrukcí, jež Objective C nabízí; ačkoli žádná z nich není pro programování bezpodmínečně nutná – jednoduché testovací prográmky jste si snadno mohli vyzkoušet už s využitím služeb, popsaných minule – dokáží programátorovi výrazně usnadnit život.

V minulém dílu jsme se seznámili se systémem objektů a ukázali jsme si všechny základní služby jazyka Objective C. Nyní se seznámíme se zbytkem konstrukcí, jež Objective C nabízí; ačkoli žádná z nich není pro programování bezpodmínečně nutná – jednoduché testovací prográmky jste si snadno mohli vyzkoušet už s využitím služeb, popsaných minule – dokáží programátorovi výrazně usnadnit život.

Několik typů, konstant a proměnných

O jednom z rozšíření, jež Objective C nabízí, jsme se již zmínili v rámci našeho příkladu "čtečky RSS na dvacet řádků": ve standardních hlavičkových souborech jsou definovány typ a hodnoty pro logické (boolovské) výrazy: jde o typ BOOL a hodnoty YES a NO. Samozřejmě jsme stále v jazyce C, takže BOOL je prostě celočíselný typ, a hodnoty YES a NO vhodná "nenula" a nula – standardní headery prostě definují

typedef signed char  BOOL; 
#define YES          (BOOL)1
#define NO           (BOOL)0

Kromě typů id a Class a hodnot nil a Nil, jež již známe z minulého dílu, nabízí Objective C ještě následující typy a hodnoty:

Typy:

  • SEL: vnitřní representace zprávy
  • IMP: metoda (přímý ukazatel na metodu, používaný pro statický přístup)

Identifikátory:

  • id self: v implementaci metody representuje objekt, který metodu zpracovává
  • id super: detto, ale jeho metody jsou vyhledávány v rodičovské třídě
  • SEL _cmd: v implementaci metody representuje zprávu, jež metodu vyvolala

Z typů je rozhodně důležitější SEL: jeho hodnoty přesně odpovídají identifikátorům zpráv; jinými slovy, existuje vzájemně jednoznačný převod mezi jmény zpráv (jako je třeba doubleValue, zpravaSJednimParametrem: či parser:didEndElement:namespaceURI:qualifiedName:) a hodnotami typu SEL. Z hlediska programátora bychom tedy stejně dobře mohli používat přímo textové řetězce, obsahující jména zpráv (podobně tomu je kupříkladu v Javě); hodnoty SEL však jsou mnohem efektivnější: jde totiž v zásadě o celá čísla (přesně řečeno, SEL je ukazatel, ovšem ukazatele jsou ve standardním C na celá čísla bezproblémově převoditelné). Díky tomu jsou dynamické služby Objective C, založené na hodnotách typu SEL – budeme jim říkat selektory – nesrovnatelně efektivnější, než obdobné služby Javy z java.lang.reflect.

Pro získání selektoru na základě známého jména zprávy slouží v Objective C speciální direktiva @selector, jejímž argumentem je konstantní jméno zprávy (např. tedy můžeme napsat "SEL foo=@selector(doubleValue);", povšimněte si, že "doubleValue" zde není textový řetězec, ale přímo jméno zprávy bez uvozovek). Pro přímé převody mezi hodnotami typu SEL a textovými objekty (s nimiž se podrobněji seznámíme až příště) slouží standardní funkce NSStringFromSelector a NSSelectorFromString – ty jsou však samozřejmě méně efektivní, než direktiva @selector, jež je de facto konstantou (přesněji pro puntičkáře, konstantní adresou, jejíž hodnota je určena ve fázi spojování modulů).

Typ IMP vlastně není ničím jiným, než klasickým Céčkovým ukazatelem na funkci, a využívá se v těch zcela výjimečných případech, kdy potřebujeme volat metodu rychleji, než prostřednictvím mechanismu zpráv.

Naproti tomu, identifikátory self a super používáme velmi často, a je důležité je správně pochopit; jejich popisu proto věnujeme samostatný odstavec. Řidčeji se setkáme s využitím identifikátoru _cmd; ten je typu SEL a uvnitř implementace nějaké metody vždy obsahuje selektor té zprávy, jejíž přijetí vedlo k vyvolání dané metody. Později si ukážeme jeho praktické využití na několika příkladech, ale pro tuto chvíli můžete na jeho existenci klidně zapomenout: pro základní pochopení Objective C není vůbec důležitý.

Za zmínku ještě stojí to, že self, super a _cmd jsou skutečně identifikátory a nikoli nová klíčová slova. Můžeme je díky tomu bez jakýchkoli problémů předefinovat; překladač Objective C kupříkladu bez problémů přeloží 'obyčejný céčkový' program, ve kterém je použita proměnná jménem "self" (jen to zkuste v C++ s proměnnou jménem "this"!).

Nil jako speciální příjemce

Nesmírně praktickou konvencí jazyka Objective C je to, že "neexistujícímu objektu" – tedy kterékoli z hodnot NULL, Nil, nil, 0 – můžeme bezpečně posílat jakékoli zprávy, a vůbec nic se nestane. Formálně, zaslání libovolné zprávy objektu na nulové adrese je zcela korektní prázdná operace, jež sama (bez ohledu na odeslanou zprávu) vrací hodnotu nil. To je obrovská výhoda např. proti Javě, v níž podobná situace vyvolá výjimku (nemluvě o C++, v němž může vyvolat naprosto jakýkoli problém, od pádu programu až po běh bez varování a nesprávné výsledky); v Objective C můžeme třeba v databázovém systému, spravujícím nějaký přehled občanů, napsat

id svagrovaTchyne=[[[[obcan manzelka] bratr] manzelka] matka];

a získáme správný výsledek i v případě, že daný občan nemá manželku, nebo ta nemá bratra, nebo její bratr není ženat: v Javě i v C++ bychom museli výraz proložit trojicí zhola zbytečných ifů.

Self a super

Zopakujme si z minulého dílu úryvek, týkající se přístupu k proměnným objektů:

Uvnitř implementace metod jsou přímo přístupné všechny vlastní proměnné, stačí uvést jejich jméno (takže kdybychom např. implementovali metodu třídy MyClass2, mohli bychom vracet hodnotu proměnné d příkazem "return d;").

Jak je to zařízeno? Ale jednoduše: kdykoli běží kód nějaké metody – tedy kód, vyvolaný zasláním nějaké zprávy nějakému objektu – adresa tohoto objektu je k dispozici ve zvláštní proměnné. Tato adresa pak samozřejmě umožní přístup k proměnným objektu komukoli, kdo "ví", kde uvnitř objektu je uložena která z proměnných: víme-li např., že "d" je třetí proměnnou v objektu (a ty dvě předchozí jsou typu int), bude ležet na adrese objektu plus 8. Proměnnou, obsahující adresu objektu, má přímo k dispozici i programátor, právě pod jménem self. Můžeme tedy naprogramovat třeba následující třídu:

@interface WhereAmI:NSObject @end
@implementation WhereAmI
-(void)report {
  printf("Jsem objekt třídy WhereAmI na adrese 0x%x\n",self);
}
@end

Zašleme-li kterémukoli objektu třídy WhereAmI zprávu report, vypíše na standardní výstup svou adresu. Je zřejmé, že pro různé objekty třídy WhereAmI bude tato adresa různá: jde o asi nejrychlejší a nejjednodušší způsob, jak např. v ladicích výpisech rozlišit, který objekt dostal zprávu a zpracovává odpovídající metodu.

Ačkoli takto proměnnou self můžeme zcela korektně použít, nejběžnější použití je trochu jiné: self nejčastěji slouží jako příjemce zpráv, jež objekt zasílá "sám sobě". Proč bychom to měli dělat? Inu, nejčastěji proto, že jsme ve třídě definovali nejprve nějaké jednoduché služby, a pak na jejich základě sestavujeme složitější – třeba takto:

@interface Angle:NSObject { // objekt representuje úhel
  float rad; // rozhodli jsme se udržovat hodnotu úhlu v radiánech
}
@end
@implementation Angle
-(float)radians { return rad; } // přístup "zvenku", viz @protected níže
-(float)degrees { // občas se může hodit získat hodnotu ve stupních
  return rad/pi()*180;
}
-(BOOL)rightAngle { // jde o pravý úhel?
  return [self degrees]==90;
}
...

Pokud nějaký objekt třídy Angle dostane zprávu rightAngle, posílá objektu self – tedy sám sobě – zprávu degrees, aby zjistil svou hodnotu ve stupních (a tu pak porovná s devadesátkou).

Jistě, v tomto případě bychom mohli skoro stejně pohodlně napsat také podmínku "rad==pi()/2"; přímý zápis ve stupních však je (alespoň pro programátory nematematiky) obvykle čitelnější. Kromě toho existují ještě důležitější důvody, proč se vždy vyplatí používat vlastních zpráv spíše, než přistupovat přímo k vnitřním proměnným: ten nejzákladnější si ukážeme hned.

Hlavní výhodou nepřímého přístupu k proměnným objektu (tedy s využitím posílání zpráv "sám sobě", a nikoli přímého zápisu samotné proměnné) je to, že posílání zpráv podléhá dědičnosti: to přináší nesmírnou flexibilitu. Dejme tomu, že po nějakém čase narazíme na problém: třída Angle nedává zrovna nejlepší výsledky pro úhly, přesahující 360°. Inu ovšem, zapomněli jsme úhly normalizovat – hned to napravíme v podtřídě:

@interface NormalizedAngle:Angle @end
@implementation NormalizedAngle
-(float)radians {
  while (rad>2*pi()) rad-=2*pi();
  return rad;
}
-(float)degrees {
  while (rad>2*pi()) rad-=2*pi();
  return rad/pi()*180;
}
@end

Ať už se rozhodneme získat hodnotu ve stupních či radiánech, nejprve ji normalizujeme (jistě, ještě bychom měli řešit záporné úhly, a také by to šlo efektivněji, ale to teď není podstatné); pak teprve vrátíme výsledek – v radiánech přímo, ve stupních převedený již známým způsobem.

Důležité je to, že díky použití "[self degrees]" vůbec nemusíme upravovat metodu rightAngle, a přesto bude fungovat zcela korektně jak ve staré, tak i v nové třídě – jinými slovy, pošleme-li zprávu rightAngle objektu třídy Angle, jenž representuje úhel 450°, vrátí NO; pošleme-li však tutéž zprávu objektu třídy NormalizedAngle, jenž representuje tutéž hodnotu, vrátí YES.

Je zřejmé, proč je tomu právě tak? Je to právě proto, že posílání zpráv bere v úvahu dědičnost: jakkoli se v obou případech po přijetí zprávy rightAngle nalezne týž kód metody (ze třídy Angle), po přijetí zprávy degrees se pro objekt třídy Angle použije jiná metoda, než pro objekt třídy NormalizedAngle. Jelikož implementace rightAngle posílá zprávu degrees sama sobě, využívá toho ke své výhodě: pro objekt třídy Angle dostaneme hodnotu ve stupních bez normalizace, pro objekt třídy NormalizedAngle dostaneme v téže metodě hodnotu normalizovanou.

Malý test pro pozorného čtenáře: implementace třídy Angle je docela nešikovná: lepší implementací jsme si mohli ušetřit trochu programování (jmenovitě celou implementaci metody degrees ve třídě NormalizedAngle). Víte co mám na mysli? Ano-li, skvělé, čtěte dál. Ne-li, pročtěte si posledních pár odstavců znovu

Posílání zpráv "sám sobě" prostřednictvím proměnné self je skvělá věc; někdy se však nedá dost dobře použít. Představme si, že bychom ve třídě NormalizedAngle chtěli považovat za pravý také úhel 270°; v nadšení z toho, jak je posílání zpráv "sám sobě" šikovné, by se programátor začátečník mohl pokusit o následující implementaci metody rightAngle ve třídě NormalizedAngle:

-(BOOL)rightAngle { // pozor, chyba
  return [self rightAngle] || [self degrees]==270;
}

Ouvej: pokusíme-li se skutečně poslat objektu takto implementované třídy zprávu rightAngle, program "zamrzne" (a po chvilce vypíše chybové hlášení, týkající se zakázaného přístupu k paměti). Už vidíte proč? Samozřejmě, spáchali jsme věčnou rekursi: metoda rightAngle pošle témuž objektu zprávu rightAngle, takže se znovu vyvolá metoda rightAngle, jež pošle témuž objektu zprávu rightAngle... a tak dále, teoreticky do nekonečna, prakticky jen dokud nepřeteče zásobník.

Řešením je právě druhý speciální identifikátor, super. Ten stejně jako self representuje týž objekt, který právě zpracovává metodu (přesněji, který dostal zprávu, jež k vyvolání metody vedla), ale zároveň přidává požadavek, že metoda, odpovídající právě posílané zprávě, se nesmí hledat v hierarchii tříd níže, než v nadtřídě třídy, v jejíž implementaci je super použito.

Zní to zmateně? Znamená to prostě "nezacyklit se"; pošle-li objekt sám sobě libovolnou zprávu pomocí identifikátoru super, nikdy se pro její zpracování nevyvolá metoda, implementovaná v téže třídě, v níž je super použito. Namísto toho to musí být v některé z jejích nadtříd. Díky tomu implementace

-(BOOL)rightAngle {
  return [super rightAngle] || [self degrees]==270;
}

bude fungovat zcela korektně: díky identifikátoru super se po odeslání zprávy rightAngle nevyvolá znovu tato metoda, ale metoda nadtřídy (v našem případě tedy třídy Angle, ta, jež pouze porovná "[self degrees]" a 90).

Bývali bychom tedy kupříkladu také mohli metodu radians ve třídě NormalizedAngle implementovat takto (a bylo by to z mnoha důvodů, jež dnes nebudeme podrobně rozebírat, lepší a korektnější):

@implementation NormalizedAngle
-(float)radians {
  float r=[super radians]; // vyvolá metodu ze třídy Angle
  while (r>2*pi()) r-=2*pi();
  return r;
}
...

Mimochodem, na rozdíl od self, což je plnohodnotná proměnná jež může být použita kdekoli, super je pouze speciální příjemce, jenž má místo jen v rámci posílání zpráv (což je nakonec samozřejmé, neboť jinde by jeho použití ani nemělo rozumný význam).

Kategorie

Vraťme se od tajů správného a špatného objektového designu zatím ještě na chvíli k vlastnímu programovacímu jazyku: velmi silný prostředek Objective C, jenž kupříkladu v takové Javě zcela chybí, jsou kategorie.

Primárním účelem kategorií je umožnit rozložení implementace jedné složité třídy do několika zdrojových souborů (potenciálně umístěných i v různých modulech). Kategorie má interface i implementaci velmi podobné těm "normálním", avšak na místě nadřízené třídy je jméno kategorie v závorkách – zde je samozřejmě povinné i pro implementaci. Kategorie nemůže definovat vlastní proměnné; má však volný přístup k proměnným, definovaným v základním rozhraní třídy.

Dejme tomu, že máme následující třídu:

@interface Xxx:Yyy { int x; }
-aaa;
-bbb;
-ccc;
@end

včetně odpovídající implementace

@implementation Xxx
-aaa { return x; }
-bbb { return 2*x; }
-ccc { return 3*x; }
@end

Pokud by pro nás bylo z jakéhokoli důvodu výhodné oddělit od sebe implementace těchto tří metod do samostatných celků, mohli bychom stejně dobře použít základní třídy a dvou kategorií – z hlediska práce s třídou Xxx a jejími instancemi by se nezměnilo vůbec nic, vše bude fungovat stejně dobře:

@interface Xxx:Yyy // základní třída
-aaa;
@end
@interface Xxx (KategorieProMetoduB)
-bbb;
@end
@interface Xxx (AProMetoduCcc)
-ccc;
@end

Ovšemže by v praxi kterákoli z kategorií mohla být v samostatném hlavičkovém souboru – ten by jen musel pomocí direktivy #import zavést hlavičkový soubor s deklarací základní třídy. Obdobně by samozřejmě byla rozdělena i implementace:

@implementation Xxx // základní třída
-aaa { return x; }
@end
@implementation Xxx (KategorieProMetoduB)
-bbb { return 2*x; }
@end
@implementation Xxx (AProMetoduCcc)
-ccc { return 3*x; }
@end

Zde by ovšem každá z kategorií jistě byla v samostatném zdrojovém souboru (jinak by bývalo nemělo valný smysl implementaci třídy do kategorií vůbec dělit).

Nejdůležitější službou, již kategorie nabízejí (a jež právě ve zmíněné Javě hrozně moc schází) je doplňování nových metod k již existujícím třídám. Ukažme si jednoduchý příklad: dejme tomu, že bychom chtěli, aby libovolný objekt dokázal reagovat na zprávu where jménem počítače, na kterém běží proces, v rámci něhož objekt existuje. V Objective C není nic jednoduššího – prostě implementujeme kategorii

@interface NSObject (ReportWhere)
-(NSString*)where;
@end
@implementation NSObject (ReportWhere)
-(NSString*)where {
  return [[NSProcessInfo processInfo] hostName];
  // se třídou NSProcessInfo se seznámíme také později
}
@end

Jakmile máme kategorii hotovou (a připojenou – třeba dynamicky – do kódu aplikace), můžeme novou službu zcela volně používat u kteréhokoli objektu kterékoli třídy (jejíž nadtřídou je NSObject, ale to platí takřka pro všechny).

Hlavní hodnota kategorií (a doplňování nových metod) spočívá v tom, že díky obohacení polymorfismu umožňují využívat korektního objektového designu a vyhnout se přímým testům tříd (tedy např. obdobě javského instanceof); takovými věcmi se však budeme zabývat až později. Někdy se také může hodit schopnost kategorií změnit implementaci již existující metody; to je zase naopak tak trošku prasárna, a smysl to má pouze pro případné opravy chyb knihoven, k jejichž zdrojovému kódu nemáme přístup.

Protokoly

Protokol v zásadě není ničím jiným, než seznamem metod; používá se jako společný prvek pro specifikaci tříd, které mají mít společné metody, ale nejsou strukturálně příbuzné (čímž nahrazuje implementačně i programátorsky obtížnou vícenásobnou dědičnost C++ v tom jediném případě, kdy měla jakýsi smysl). Programátoři v Javě vlastně protokoly znají, jen jim říkají interfaces a mají k dispozici jen omezené služby (např. nemohou do interfaces ukládat metody tříd).

Protokol je definován velmi podobně jako interface (ten "náš", z Objective C), nemůže však samozřejmě obsahovat proměnné. Protokoly zato mohou mít svou vlastní dědičnost, a ta – na rozdíl od dědičnosti tříd – může být i vícenásobná (jelikož protokol nemá vlastní datový obsah, nepřináší to vůbec žádné problémy). Namísto direktivy @interface je zde použita direktiva @protocol, a případný seznam "nadprotokolů" se píše do lomených závorek:

@protocol PlusMinus
-plus:objekt;
-minus:objekt;
@end
@protocol KratDeleno
-krat:objekt;
-deleno:objekt;
@end
@protocol Aritmetika <PlusMinus, KratDeleno>
-unarniMinus;
@end

Protokol Aritmetika tedy obsahuje všech pět metod (plus:, minus:, krat:, deleno:, unarniMinus).

Do lomených závorek se píše seznam jmen protokolů také v interface třídy, jež má dané protokoly implementovat:

@interface Xyz:NSObject <Aritmetika>
...

Překladač pak hlídá, zda v implementaci skutečně máme všechny metody ze všech protokolů (a není-li tomu tak, vydá varování). Varování si můžeme vyžádat i v případě, kdy bychom objektu, jenž sice může být libovolné třídy, ale ta musí odpovídat některému ze známých protokolů, posílali zprávu, jež součástí protokolu není – jen musíme namísto prostého typu id použít typ kvalifikovaný jmény protokolů, opět v lomených závorkách:

id foo;
id<Aritmetika> bar;
[foo intValue]; // žádné varování, foo může být třeba číslo
[bar intValue]; // varování: v protokolu Aritmetika není metoda intValue

Objective C nabízí ještě jedno použití direktivy @protocol: stojí-li bezprostředně za ním jméno protokolu v závorkách, získáme tak speciální objekt, který protokol representuje za běhu programu, a jehož prostřednictvím můžeme např. ověřit, zda neznámý objekt danému protokolu odpovídá nebo ne:

if ([foo conformsToProtocol:@protocol(Aritmetika)]) [foo plus:bar];

Je vhodné si uvědomit zásadní rozdíl mezi výše popsaným varováním překladače při použití deklarace id<...> a mezi testem pomocí zprávy conformsToProtocol:: varování překladače je přesně to, co nabízí např. C++: šikovná pomůcka, ale není a v principu nikdy nemůže být stoprocentně spolehlivé, neboť reflektuje pouze ty informace o objektu, jež jsou známy v době překladu. Pokud však kupříkladu deklarujeme proměnnou jako id<PlusMinus> ale ve skutečnosti pak je do proměnné uložen objekt, který protokolu neodpovídá, překladač žádné varování neohlásí (neboť to nemůže "vědět"), ale k chybě za běhu samozřejmě dojde. Běhový test pomocí metody conformsToProtocol: je naproti tomu absolutně spolehlivý, neboť je polymorfně založen na skutečném stavu objektu: správnou odpověď dostaneme vždy, bez ohledu na to, jak je objekt foo deklarován a zda jeho skutečný obsah této deklaraci odpovídá nebo ne.

Ostatní

Nakonec se zběžně zmíníme o několika zbývajících, nepříliš často užívaných vlastnostech jazyka. První z nich je direktiva @class; ta umožňuje užívat odkazů na třídy v dopředných referencích, tedy dříve, než překladač narazí na odpovídající direktivu @interface. To může být zapotřebí třeba tam, kde se dvě různé třídy nějak odkazují na sebe navzájem:

@class Xxx;
@interface Yyy
-(Xxx*)xxx;
@end
@interface Xxx
-(Yyy*)yyy;
@end

Proměnné objektu mohou být k dispozici pouze jeho vlastním metodám, nebo i metodám všech jeho dědiců (to jsme viděli v původní implementaci metody radians třídy NormalizedAngle), nebo – ve výjimečných případech, kdy z nějakého důvodu musíme rezignovat na objektové programování a využívat statické programátorské techniky – mohou být proměnné přístupné z jakéhokoli úseku kódu kdekoli. Možnosti přístupu k proměnným jsou určeny použitím jedné ze tří direktiv:

  • @private: proměnné jsou přístupné pouze metodám objektu samotného;
  • @protected: proměnné jsou přístupné i dědicům (tento přístup platí také nepoužijeme-li žádnou z direktiv);
  • @public: proměnné jsou přístupné komukoli.

Jestliže z nějakého důvodu musíme rezignovat na objektový přístup, můžeme také získat neomezený přístup k proměnným kteréhokoli objektu pomocí direktivy @defs; ta se však používá tak zřídkakdy, že si její použití ani nebudeme podrobně popisovat. Podobně je tomu s direktivou @encode, jež slouží pro dynamickou identifikaci typu: dříve, než budete kteroukoli z těchto direktiv skutečně potřebovat, budete přesně vědět, kde si je najít

Pro práci s distribuovanými objekty nabízí Objective C kvalifikátory in, out, inout pro deklaraci ukazatelů, kvalifikátory byref a bycopy pro deklaraci objektů, a kvalifikátor oneway pro asynchronní zprávy. O co jde si vysvětlíme až se budeme zabývat distribuovanými objekty samotnými.

Novinkou současné verse překladače je přímá podpora výjimek a kritických sekcí na úrovni jazyka s využitím direktiv @try, @catch, @finally a @throw a @synchronized. Ani těm se nebudeme věnovat nyní, ale až ve článcích, popisujících odpovídající služby.

Poslední specialitou Objective C je schopnost vytvářet statické objekty reprezentující textové řetězce na základě zápisu @"... text ...". Již jsme se o tom zběžně zmínili; podrobněji se na to podíváme příště, až si ukážeme, jak přesně v Objective C objekty vznikají a zanikají.

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Začínáme s  

 

 

 

Nejčtenější články
Nejlépe hodnocené články
Apple kurzy

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: