Více o XIBech - 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ů



Software

Více o XIBech

24. srpna 2011, 00.00 | V předchozím dílu našeho seriálu o programování počítačů s operačními systémy Mac OS X a iOS ve vývojovém prostředí Xcode jsme se začali zabývat "XIBy", sloužícími převážně (ale ne výhradně) k tvorbě grafického uživatelského rozhraní. Dnes si toho o nich řekneme více.

Minule jsme si podrobně vysvětlili, co je to NIB a XIB a jaký je mezi nimi vztah. Víme, že jde prakticky o totéž, NIB je skupina archivovaných objektů (typicky, ale ne nutně, objektů tvořících GUI), jež se jako celek načte do aplikace; XIB je pouze textová podoba NIBu, používaná při programování pro výhody textových formátů při správě a údržbě projektů. V následujícím textu proto budeme užívat obou termínů záměnně – "obsah NIBu" nebo "obsah XIBu" je totéž, rozdíl mezi nimi budeme činit jen ve výjimečných případech, kdy bude hrát roli to, zda hovoříme o projektu před sestavením aplikace nebo o sestavené aplikaci.

Když jsme si ukázali princip na praobyčejném objektovém archivu, řekli jsme si také, že skutečný NIB nabízí pár specializovaných služeb pro větší pohodlí. Dnes se na ně podíváme trochu podrobněji.

Vazby mezi objekty

Žádný objekt není ostrov sám pro sebe; užitečným se stává teprve díky vazbám na ostatní objekty, s nimiž komunikuje (a jež komunikují s ním) prostřednictvím zpráv. Nejjednodušší a nejběžnější vazba je přímý odkaz na jiný objekt – prostě instanční proměnná nebo atribut typu id (nebo, samozřejmě, v praxi častěji VhodnáTřída*), obsahující adresu spolupracujícího objektu.

Vazby uvnitř XIBu

Hned zpočátku je vhodné si uvědomit, že objektové vazby mezi objekty uvnitř XIBu nejsou žádný problém a korektně fungují i bez jakýchkoli doplňků nebo rozšíření.

Stejně jako objekt při archivaci ukládá do archivu své atributy typu rozměrů rámce nebo textu, zobrazovaného v instanci třídy UILabel, ukládá také odkazy na ostatní objekty, jež udržuje ve svých instančních proměnných nebo atributech. V dílu našeho seriálu věnovaném archivaci objektových sítí jsme si ukázali zprávu encodeConditionalObject:forKey:, jež slouží právě pro tento účel – a můžeme se samozřejmě spolehnout, že archivované objekty ji budou korektně používat.

Příkladem takové vazby, jež zůstává bez problémů v archivu zachována, je třeba právě struktura rámců, již jsme si ukázali v našem příkladu minule – to, že okno obsahuje dvě instance třídy UILabel a jeden UIButton není ničím jiným.

Vnější vazby

Horší je to s vazbami, jež spojují objekty v XIBu s objekty mimo něj. Služba encodeConditionalObject:forKey: takový externí odkaz samozřejmě do objektového archivu neuloží; po načtení se pak na jeho místě objeví hodnota nil. O vazbách vedených z vnějších objektů do XIBu samozřejmě ani nemá smysl hovořit: tyto objekty – existují-li vůbec – se o rozbalení objektového archivu vůbec "nedozvědí", a jejich vazby tedy zůstanou prázdné.

Ačkoli by bylo v principu možné tyto vazby vždy po načtení NIBu do paměti obnovit programově, bylo by to nepohodlné. Zde proto nastupuje prvá ze specializovaných služeb pro větší pohodlí: zástupné objekty.

Vlastně je to celkem jednoduché: nyní už samozřejmě nepředpokládáme, že bychom objektovou síť sestavovali programově a pak ukládali do souboru; namísto toho ji budeme vždy vytvářet ve specializovaném editoru. Díky tomu není problém do této sítě přidat jeden speciální objekt navíc – půjde o instanci nějaké naší speciální třídy, takové, aby instance mohla obsahovat libovolné vazby na libovolné objekty. Technicky to není vůbec žádný problém; prostě místo toho, abychom tyto vazby ukládali do instančních proměnných, použijeme pro ně třeba atribut bindings třídy NSMutableDictionary, kde vazbu "jež patří do instanční proměnné foo" uložíme pod klíč "foo". Při ukládání archivu pak právě tuto naši speciální instanci použijeme jako "root object" – vazby na vše ostatní, co v archivu potřebujeme (např. tedy v našem příkladu na okno, jež chceme zobrazit), budou navázány právě na ni.

Pak využijeme toho, že API nabízí služby pro přístup k atributům nebo instančním proměnným objektu podle jména – podrobně jsme si toto API, nazývané Key/Value Coding nebo zkráceně KVC, popsali již před časem – a můžeme načítání objektového archivu naprogramovat zhruba takto (jde jen o koncepci, v praxi by kód byl samozřejmě trochu složitější kvůli technickým drobnostem):

-(BOOL)loadArchiveFromPath:(NSString*)path owner:owner {
  id root=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
  if (!root) return NO;
  NSDictionary *bd=[root bindings];
  for (NSString *key in bd)
    [owner setValue:[bd objectForKey:key] forKey:key];
  return YES;
}

Nyní je zřejmé, že všechny objektové vazby, jež vedly z našeho zástupného objektu do ostatních objektů v archivu jsou korektně přeneseny do "lokálního" objektu owner (pokud má vhodný atribut nebo instanční proměnnou; nemá-li je, dojde k chybě).

Ještě je třeba vyřešit podobným způsobem naopak vazby z ostatních objektů v archivu na root (kód by byl velmi podobný, opět je třeba, aby byl v "rootu" seznam těchto vazeb, a pak se pomocí metody setValue:forKey: nahradí ve všech nově načtených objektech odkaz na objekt "root" odkazem na objekt "owner") a jsme hotovi: nejenže je objektová síť korektně načtena, ale také jsou korektně navázána všechna propojení mezi ní a objektem, který jsme vybrali jako jejího vlastníka.

Za zmínku možná stojí pár drobností:

• na přesně témže principu můžeme do archivu uložit více různých zástupných objektů;

• povšimněte si, že jsme ve výše uvedeném pseudokódu načtené objektové síti neposílali zprávu retain: je samozřejmě věcí objektových vazeb v "owneru", aby si cílové objekty přidržely samy;

• v důsledku minulého bodu automaticky zanikne i "root";

• skutečná třída "ownera" vůbec nehraje roli: podstatné je, aby dokázal interpretovat korektně nastavení všech atributů jako příjemce zprávy setValue:forKey:; jak přesně to ale udělá je lhostejné. Stejně dobře to může být instance třídy obsahující odpovídající instanční proměnné, nebo NSMutableDictionary – nebo třeba nějaká třída, v níž jsou implementovány odpovídající settery jako třídní metody!

Mimochodem, výše popsaný mechanismus s "neretainovanými" daty, jež si musí "podržet" vlastník, se skutečně používá v iOSu. V prostředí Cocoa systému Mac OS X je tomu z historických důvodů naopak – tam jsou kořenové objekty z NIBu automaticky "retainovány", a vlastník se musí postarat o jejich uvolnění, pokud to je zapotřebí (typicky třeba pokud NIB representoval obsah okna a toto okno se zavírá).

A co "akce"?

Vazby mezi objekty ovšem nejsou pouze jednoduché odkazy; naprosto běžně se setkáváme také s "akcemi" – tedy s kombinací zprávy, již je zapotřebí odeslat, a cílového objektu, jemuž se tato zpráva má doručit: typicky např. stanovíme, že po stisknutí toho a toho tlačítka dostane objekt foo zprávu bar.

Komplikuje nám to nějak výše popsaný mechanismus?

Vůbec ne.

Akce ve skutečnosti není nic jiného, než kombinace dvou proměnných: odkazu na objekt – přesně a do posledního detailu stejnému odkazu, jako ty, o nichž jsme se bavili před chvilkou – a proměnné typu SEL, obsahující selektor zprávy, již je zapotřebí objektu poslat (o selektorech jsme se bavili hned na samém začátku, když jsme si vysvětlovali, jak funguje jazyk Objective C).

Jediná speciální podpora, již tedy potřebujeme pro "akce", je na úrovni editoru XIBů: ten musí být schopen pohodlným způsobem editovat celou dvojici proměnných najednou jako "akci poslanou objektu". Z hlediska vnitřní struktury XIBu však nám zcela stačí to, co máme už dávno k dispozici.

(V iOSu je to jen malinko složitější po technické stránce, neboť zde objekty mnohdy dovolují současné odeslání více různých akcí více různým cílům. Na principu věci to ale nemění zhola nic; jen musíme počítat s tím, že místo jednoduchého odkazu na jeden cílový objekt a jednoduché proměnné typu SEL máme v obou případech pole o libovolném počtu prvků.)

Specifické podtřídy

Poslední se specialisovaných služeb pro větší pohodlí, jež si podrobněji vysvětlíme, je podpora pro specifické podtřídy.

Dejme tomu, že v našem příkladu měl na místě prvé instance UILabel ve skutečnosti stát objekt třídy MySmartLabel, který rozšiřuje standardní UILabel o nějaké formátování, specifické pro naši konkrétní aplikaci.

V původní variantě, kdy jsme kompletní GUI sestavili programově přímo v cílové aplikaci a tam také uložili do archivu (a z něj opět načtli), bylo samozřejmě vše v pořádku.

Problém ale nastane ve chvíli, kdy tento archiv budeme chtít otevřít v obecném editoru objektových sítí: třída MySmartLabel v tomto editoru samozřejmě není k dispozici, a načítání archivu skončí chybou.

Co s tím?

Už před lety našli ve firmě NeXT elegantní a účinné řešení: při archivaci NIBu objekty těchto speciálních tříd neukládáme přímo; namísto toho

• třídu nahradíme její podtřídou, jež ve standardních knihovnách k dispozici je (v našem případě tedy UILabelem, jindy by to mohlo být třeba UIView nebo dokonce NSObject);

• doplníme ji ale záznamem, obsahujícím jméno skutečně požadované třídy;

• a k tomu ještě přidáme seznam "outletů" a "akcí", jež skutečně požadovaná třída má navíc oproti té, kterou jsme ji nahradili.

Editor XIBů pak jen vezme tento seznam v úvahu a objekt – který nyní hraje tak trochu roli zástupného – opatří podobným slovníkem pro navazování "neznámých" odkazů, jaký se používá u zástupných objektů: to proto, aby bylo možné pracovat s odkazy oné neznámé podtřídy.

To je vlastně celé: při načítání NIBu do aplikace se samozřejmě použije skutečné jméno požadované třídy, a objektové vazby se opět naváží pomocí slovníku, podobně, jako tomu bylo u objektů zástupných.

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

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Tipy a Triky  

 » Rubriky  » Začínáme s  

 » Rubriky  » Software  

 

 

 

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

 

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

Uživatelské jméno:

Heslo: