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

 

Jaký fotograf/ka získal/a cenu za nejpopulárnější příspěvek v Nikon Photo Contest?

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  

Diskuse k článku

 

Vložit nový příspěvek   Sbalit příspěvky

 

Memory mngmnt

Autor: hroch32 Muž

Založeno: 21.09.2011, 09:02
Odpovědí: 0

Dovolil bych si drobně nesouhlasit s tvrzením, že "...jak v iOSu, tak i v Mac OS X se ovšem musíme v metodě dealloc postarat o jejich uvolnění...", které je na počátku sekce "Outlety". To platí (alespoň u OS X) pouze pro tzv. "top level objects", tzn. ty objekty, které jsou v IB vidět v okně souboru (zpravidla nějaké to okno nebo rámec, kontroléry ap.). Jakékoliv vnořené objekty (typicky NSButton ap.) jsou automaticky nastavené tak, že jsou retainované pouze svým "rodičem", takže když zanikne ten, zanikne i daný objekt. Jejich ručním releasováním by došlo na 99 % k pádu aplikace (záleží na tom, kdy by se tak dělo).

Možná jsem ten odstavec jen špatně pochopil, v tom případě se omlouvám. Každopádně je v tomto případě vhodné přečíst si dokument "Resource programming guide", sekci "Nib files", protože těch pouček a pravidel je kolem nibů docela dost a je dobré se v nich orientovat.

Odpovědět na příspěvek

RE: Memory mngmnt

Autor: OC Muž

Založeno: 21.09.2011, 09:08

Já to možná napsal nesrozumitelně, ale...

... uvolnit musím to, co jsem retainoval. A objekt, který mám nadrátovaný na ivar outlet (nebo retain atribut), jsem retainoval -- to, že nepřímo, na věci nic nemění.

U toho retain atributu je to celkem zřejmé, ale u toho ivaru se na to rádo zapomíná.

Odpovědět na příspěvek

RE: RE: Memory mngmnt

Autor: hroch32 Muž

Založeno: 21.09.2011, 09:15

Pravda, při loadování nibu se vlastníkům outletů posílá setOutletName:, čili tam ano. Takže jsem to zas jenom špatně pochopil :-)

Odpovědět na příspěvek

Používání přístupových metod v init/dealloc

Autor: hroch32 Muž

Založeno: 21.09.2011, 09:10
Odpovědí: 0

Osobně jsem si na použití accessorů v metodách init a dealloc párkrát docela natloukl ústa, takže radím se toho spíš vyvarovat. Záleží samozřejmě na situaci, ale jakmile má člověk nějaký ručně implementovaný setter, je lepší ho v těchto případech nepoužívat. To samé platí, pokud je používáno KVC/KVO.

Pokud někoho otravuje, že musí používat dva příkazy ([foo release]; foo = nil;) může si udělat pěkné makro a má pokoj. Ono se totiž hodí i jindy:
#define MY_RELEASE(x) { [x release]; x = nil; }

Ve složených závorkách je to proto, že je tím umožněno použití po podmínce, aniž by člověk musel nutně použít složené závorky v kódu:
if (!success)
MY_RELEASE(foo);

Odpovědět na příspěvek

RE: Používání přístupových metod v init/dealloc

Autor: OC Muž

Založeno: 21.09.2011, 14:30

Stran přístupových metod, mám názor (a zkušenosti) odlišné, ale to je svým způsobem jedno: Apple důrazně podporuje konvenci je v init/dealloc nepoužívat, tak to tak také vysvětluji.

Stran makra, zkus to s else, podivíš se :)

Nicméně i na to je už asi sto let známá finta, dělá se to takhle:

#define xxx do { ... } while(0)

V GCC/LLVM by šly i jiné finty, ale toto má výhodu, že to chodí v libovolném C.

Odpovědět na příspěvek

RE: RE: Používání přístupových metod v init/dealloc

Autor: hroch32 Muž

Založeno: 21.09.2011, 17:11

S else jsem to mnohokrát zkoušel a nikdy se nedělo nic, co bych nepředpokládal. Copa by se mělo dít?

Odpovědět na příspěvek

RE: RE: RE: Používání přístupových metod v init/dealloc

Autor: OC Muž

Založeno: 21.09.2011, 18:01

Syntax error se stane, a to zcela spolehlivě.

Nebo si musíš sám pamatovat, jak to makro je napsané, a dávat si bacha, abys za ně při použití nenapsal středník jako za každé jiné, což je kapku dost ošajstlich a poněkud přes ruku :)

Odpovědět na příspěvek

RE: RE: RE: RE: Používání přístupových metod v init/dealloc

Autor: OC Muž

Založeno: 21.09.2011, 18:05

P.S. Nemluvě ani o tom, že v tomto konkrétním případě je to celé naprosto zbytečné cvičení a stačí

#define foo(x) [x release],x=nil

a je to také OK (jsi-li hodně paranoidní stran možných hodně šílených použití, můžeš to ještě dát do normálních kulatých závorek) :)

Odpovědět na příspěvek

RE: RE: RE: RE: RE: Používání přístupových metod v init/dealloc

Autor: hroch32 Muž

Založeno: 22.09.2011, 00:53

Syntax error se nestane a to vcelku spolehlivě :-) Fakt nikdy, mám to přesně takhle definované. Schválně jsem si i udělal cvičný projekt a i v něm to projde, definované přesně, jak jsem psal výše (jak v GCC, tak v LLVM – obojí Xcode 3.1, 3.2 i 4.0).

Možností jak to napsat je spousta, to je jasné. Ale nasadils mi brouka do hlavy, jestli to nemám udělat pomocí toho do-while. Paranoiou sice netrpím, ale sledujou mě ;-)

Odpovědět na příspěvek

RE: RE: RE: RE: RE: RE: Používání přístupových metod v init/dealloc

Autor: OC Muž

Založeno: 22.09.2011, 02:53

Ale no tak, vždyť je to zřejmé :/

if (...) foo(bar); else... // nepude
if (...) foo(bar) else... // pude, ale je nekonsistentní

Odpovědět na příspěvek

RE: RE: RE: RE: RE: RE: RE: Používání přístupových metod v init/dealloc

Autor: hroch32 Muž

Založeno: 24.09.2011, 10:37

Jo táák. Nj, to bude asi tím,že já to samozstatně používám (evidentně tedy vždy) za tím else :) Když to mám za if, je to doprovázeno ještě jiným příkazem a tudíž v samozsatném bloku. Ale připomínku beru, makro pozměním, díky.

Odpovědět na příspěvek

ARC

Autor: pf Muž

Založeno: 04.10.2011, 10:03
Odpovědí: 0

Výraz "třičtvrtěautomatický garbage collector ARC" mě vcelku pobavil (nedávno sem si o něm konečně něco přečetl http://bit.ly/ouh41L )

Odpovědět na příspěvek

 

 

Vložit nový příspěvek

Jméno:

Pohlaví:

,

E-mail:

Předmět:

Příspěvek:

 

Kontrola:

Do spodního pole opište z obrázku 5 znaků:

Kód pro ověření

 

 

 

 

 

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

Uživatelské jméno:

Heslo: