Vazby mezi objekty v kódu - 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:

Soutěž

Sponzorem soutěže je:

IDIF

 

Odkud pochází fotografka Anne Erhard?

V dnešní soutěži hrajeme o:

Seriály

Více seriálů



Software

Vazby mezi objekty v kódu

21. září 2011, 00.00 | V minulém dílu našeho programátorského seriálu jsme si ukázali, jak v Xcode sestavovat vazby mezi objekty; dnes se podíváme podrobněji na odpovídající implementaci v kódu.

"Drátování" mezi jednotlivými objekty v XIBech, jež umožní naplnění "outletů" a nastavení "akcí", jsme viděli podrobně minule. To samo ale stačí pouze v případech, kdy máme hotový objekt, nabízející nějaké události, schopné odesílat akce – třeba tlačítko – a také objekt, který potřebnou akci již kompletně implementuje.

Jakkoli takových objektů je na standardních knihovnách poměrně dost, daleko běžnější v iOSu je to, že většinu akcí i "outletů" implementujeme sami ve vlastních třídách (typicky jde o podtřídy UIViewControlleru, mnohdy také o vlastní rámce jako podtřídy UIView; jiné třídy v běžnějších projektech využíváme poměrně zřídkakdy).

Dnes se tedy podíváme podrobně na to, jak vypadá odpovídající kód.

Outlety

S "outlety" je situace celkem prostá. Vůbec nejjednodušší možný "outlet" je prostě instanční proměnná vhodného typu, označená v kódu navíc značkou IBOutlet – tu sice překladač Objective C ignoruje, ale Xcode podle ní rozezná, že má přístup k takové proměnné zajistit v XIBu:

@interface MyController:UIViewController {
  IBOutlet UIButton *button;
}
@end

(Poznamenejme hned, že z hlediska zapouzdření obecně deklarace instančních proměnných v současném Objective C překládaném pomocí LLVM nepatří do hlavičkového souboru a za direktivu @interface, nýbrž do implementace a za direktivu @implementation; Xcode 4.0 zde však ještě nedokáže "outlety" najít. Dokud tedy nepřejdeme na novou versi vývojového prostředí – přičemž ale 4.1 je k dispozici pouze pro Lion, a 4.2 pouze v rámci bety iOS5 – musíme se zatím s deklaracemi "outletů" a "akcí" v hlavičkových souborech smířit.)

Je třeba mít na paměti, že při zavádění NIBu do paměti se tímto způsobem deklarovaná instanční proměnná vždy plní pomocí mechanismu KVC, který takto odkazovanému objektu vždy pošle zprávu retain. To znamená, že

• v iOSu, který ukládá obsah NIBů do autorelease poolu, se u objektů, jež takto navážeme na nějaký outlet, nemusíme obávat jejich ztráty;

• jak v iOSu, tak i v Mac OS X se ovšem musíme v metodě dealloc postarat o jejich uvolnění (pokud nevyužíváme služeb "třičtvrtěautomatického garbage collectoru" ARC, jímž se budeme zabývat až později);

• v iOSu také nesmíme zapomenout objekty načtené z NIBu, který je spravován třídou UIViewController (nebo některou z jejích podtříd) uvolnit v metodě viewDidUnload.

Chceme-li si tuto práci poněkud zjednodušit, můžeme využít alternativní deklarace "outletu" jako atributu; z hlediska objektového designu je to i čistší, ačkoli je to malinko víc psaní:

@interface MyController:UIViewController
@property (assign) IBOutlet UIButton *button;
@property (retain) IBOutlet UIView *alternativeView;
@end

(Samozřejmě zde nesmíme zapomenout v implementaci použít direktivu @synthesize, jež pro takto deklarovaný atribut vytvoří jak instanční proměnnou, jež nese jeho hodnotu, tak i odpovídající přístupové metody.)

Využijeme-li deklaraci s přepínačem assign, pak objekt, který navážeme na "outlet", zprávu retain nedostane – to je ideální v (daleko nejběžnějším) případě, kdy navazujeme "outlety" nějaké podtřídy UIViewControlleru na objekty uvnitř jeho hlavního rámce. Tyto objekty nám nezaniknou, protože si je "retainuje" hlavní rámec; ten sám je pak automaticky držen při životě knihovním kódem třídy UIViewController a nám zbývá pouze jediná povinnost – odkazy vynulovat v metodě viewDidUnload:

-(void)viewDidUnload {
  [super viewDidUnload];
  self.button=nil;
}

(A opět poznámka: díky deklaraci assign by zde byl v principu korektní i kód bez "self."; je ale vždy lepší používat přístupové metody, než sahat rovnou na instanční proměnné, vyjma v metodách init a dealloc.)

Metodu dealloc v tomto případě nepotřebujeme vůbec.

Naopak pro ostatní objekty – typicky kořenové rámce odlišné od hlavního apod. – je vhodné využít deklaraci s přepínačem retain; ta pak funguje stejně, jako výše uvedená instanční proměnná, tedy musíme implementovat uvolnění jak v metodě viewDidUnload (ale na rozdíl od instanční proměnné zde můžeme použít přístupovou metodu, tj. přesně stejně jako v minulém případě napíšeme jen "self.alternativeView=nil"), tak i v metodě dealloc.

Zde je podle firmy Apple (osobně s tím nesouhlasím) vhodnější přístupovou metodu nepoužívat pro risiko vedlejších efektů při případné reimplementaci v podtřídě; metoda by tedy mohla vypadat zhruba nějak takto:

@implementation MyController
@synthesize button=_button,alternativeView=_alternativeView;
...
-(void)dealloc {
  [_alternativeView release],_alternativeView=nil;
  [super dealloc];
}
@end

(I zde se vyplatí přičinit poznámku: předně, je vhodné název instanční proměnné od názvu atributu odlišit; použití podtržítka je běžnou konvencí a v tomto případě na něm není nic špatného. Dále pak, je vhodné po uvolnění instanční proměnnou vynulovat. Ani jedno není nutné; děláme-li to ale, podstatně snížíme risiko nepříjemných chyb.)

Kolekce outletů

Kolekce outletů mohou být deklarovány opět jako atributy nebo instanční proměnné; jejich typ musí být NSSet nebo NSArray. Ukažme si už jen variantu s atributem; přímá deklarace instanční proměnné by vypadala podobně (a atributy jsou beztak koncepčně čistší):

@interface MyController:UIViewController
@property (retain) IBOutletCollection(UILabel) NSSet *labels;
@end

V závorce za značkou IBOutletCollection uvádíme typ objektu, které pak bude v editoru XIBů možné do kolekce ukládat. Stejně jako tomu je u značky IBOutlet, i značku IBOutletCollection(...) překladač ignoruje; zpracuje ji ale Xcode a řídí podle ní funkci editoru XIBu, který odpovídající třídu obsahuje.

Je vhodné mít na paměti dvě drobnosti:

• v tomto případě nemá nikdy smysl deklarace s přepínačem assign – pole odkazů samo ovšem není součástí žádného rámce, a pokud bychom si je nepřidrželi přepínačem retain, bylo by automaticky uvolněno. Musíme je tedy také explicitně uvolnit v obou metodách viewDidUnload i dealloc;

• i pokud použijeme proměnnou typu NSArray, jsou v ní objekty fakticky neuspořádané (resp. jejich pořadí je "náhodné", speciálně neodpovídá pořadí, v němž kolekci outletů v XIBu naplníme).

Objekty je tedy třeba rozlišit jinak (je-li to vůbec zapotřebí), např. podle tagů.

Akce

Pro deklaraci akcí slouží standardní značka IBAction, již překladač interpretuje přesně stejně jako typ void, ale Xcode podle ní opět řídí vlastní editor XIBů:

@interface MyController:UIViewController
-(IBAction)buttonPressed:sender;
-(IBAction)anotherButton:(UIButton*)button;
-(IBAction)otherAction;
@end

Prvý z formátů s beztypovým (tedy typu id) argumentem sender je archetypální deklarace akce, s jakou se setkáme nejčastěji – ale jen málokdy je opravdu vhodná, používá se spíše ze setrvačnosti.

Praktičtější je druhá varianta, v níž argument opatříme odpovídajícím typem – funguje přesně stejně dobře, ale za prvé je "samodokumentováno", jaká třída bude tuto akci posílat, a také při implementaci můžeme používat atributy bez přetypování (např. "button.tag" – varianta "sender.tag" uvnitř implementace prvé z akcí by bohužel vinou nedomyšleného designu Objective C nebyla možná).

Konečně třetí varianta bez argumentu se hodí tam, kde nás odesílající nezajímá (obvykle proto, že jej stejně známe, a máme jej v některém z "outletů"). To je v praxi daleko nejběžnější případ; bohužel ale je v současnosti podporována pouze v iOSu a nikoli v Mac OS X.

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  

 

 

 

 

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

Uživatelské jméno:

Heslo: