Nastal čas na kakao - Vznik a zánik objektů - 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ů



Informace

Nastal čas na kakao - Vznik a zánik objektů

26. května 2004, 00.00 | V minulém dílu našeho seriálu jsme dokončili popis vlastního jazyka Objective C. Dnes si řekneme více o základních vlastnostech všech objektů; stojí za to zdůraznit, že tyto služby jsou již hodně specifické právě pro Objective C v Mac OS X – pokud náhodou máte k dispozici nějakou jinou implementaci Objective C, mohou v ní pravidla pro vznik a zánik objektů být dost odlišná.

V minulém dílu našeho seriálu jsme dokončili popis vlastního jazyka Objective C. Dnes si řekneme více o základních vlastnostech všech objektů; stojí za to zdůraznit, že tyto služby jsou již hodně specifické právě pro Objective C v Mac OS X – pokud náhodou máte k dispozici nějakou jinou implementaci Objective C, mohou v ní pravidla pro vznik a zánik objektů být dost odlišná. Řada informací však je i tak relevantní pro libovolné objektové prostředí; konkrétní řešení problémů, o nichž se zde zmíníme, však v jiných prostředích mohou být diametrálně odlišná. Např. jazyk Java využívá toho, že v něm neexistuje "ukazatel", a díky tomu může mít plně automatický garbage collector, o jehož funkci se programátor vůbec nemusí starat (platí se za to ovšem zase jinými nevýhodami a problémy).

Vznik objektů

Už v minulých dílech jsme si řekli, že objekty vznikají na základě požadavků, poslaných jejich třídám; dokonce jsme si v úvodním příkladu ukázali i zprávu alloc, jež se právě o tvorbu objektů stará: obecně platí, že pošleme-li jakékoli třídě zprávu alloc, třída vytvoří novou instanci a vrátí nám ji. Taková instance je zcela prázdná (můžeme se jen spolehnout, že její proměnné obsahují nuly, nic jiného); proto ji musíme ještě inicializovat pomocí některé zprávy init... Těch již může být více a mohou se pro různé třídy lišit: tak třeba vytváříme-li pole objektů, máme k dispozici zprávu initWithObject:, jež inicializuje jednoprvkové pole, obsahující zadaný objekt; vytváříme-li textový řetězec, máme k dispozici zprávu initWithContentsOfFile:, jež vytvoří textový řetězec, jehož obsahem je obsah zadaného souboru. Řadu jednoduchých objektů navíc lze inicializovat prostou zprávou init bez parametrů. Prozatím si zapamatujte, že užíváme-li kombinace zpráv alloc a init..., je nutné je vždy spojit do jediného výrazu, takto:

id novyObjekt=[[Trida alloc] init...];

Proč je tomu právě tak si vysvětlíme později, až se budeme učit správně implementovat vlastní třídy, a až si budeme popisovat, jak se metody init... správně píší.

V praxi však kombinaci zpráv alloc a init... tak často neužíváme: častější jsou kombinované zprávy, jež si vyžádají vytvoření vhodného objektu od třídy "naráz" (tyto zprávy samy vnitřně volají vše potřebné, včetně alloc a init...). Platí konvence, že jména těchto zpráv začínají jménem třídy (bez případné předpony NS), za nímž jsou parametry With... podobně, jako ve zprávách initWith... Např. pro vytvoření jednoprvkového pole spíše než "[[NSArray alloc] initWithObject:o]" použijeme výraz "[NSArray arrayWithObject:o]"; pro vytvoření textového řetězce s obsahem souboru file nejspíše použijeme výraz "[NSString stringWithContentsOfFile:file]". Každá třída nabízí trochu jinou skupinu těchto kombinovaných zpráv, stejně jako nabízí trochu jinou skupinu inicializátorů initWith... Podobně, jako pro jednoduché třídy obvykle existuje inicializátor init bez argumentů, obvykle pro ně existuje i kombinovaná zpráva jméno třídy – např. prázdné pole vytvoříme výrazem "[NSArray array]", prázdný textový řetězec výrazem "[NSString string]". Nejenže je tento způsob pohodlnější, než kombinování zpráv alloc a init..., navíc automaticky využívá výhod standardní správy paměti, o níž si řekneme více později – při kombinaci zpráv alloc a init... se o správu paměti musíme postarat ručně.

Existují i jiné způsoby vytváření objektů, avšak ty se užívají dosti výjimečně (nebo se týkají pouze speciálních skupin objektů, např. třídy vytváříme prostě tím, že do některého ze zdrojových souborů zapíšeme odpovídající blok @implementation ... @end), a zmíníme se o nich později na patřičných místech.

Jen pro úplnost se zmíníme o trochu zastaralé zprávě new: jde vlastně o přežitek z verse vývojového prostředí staré několik desítek let: pošleme-li nějaké třídě zprávu new, je to naprosto přesně totéž, jako kdybychom jí poslali kombinaci alloc/init bez parametrů. Nic více, nic méně.

Doba trvání a zánik objektů

Velmi důležitým atributem kteréhokoli objektu je doba jeho existence: kdy a jak objekt zanikne? Je jeho vznik — nebo zánik — vedlejším efektem některé jiné akce programu, nebo si jej musí programátor vyžádat? Z tohoto hlediska můžeme objekty rozdělit v zásadě do čtyř skupin. První tři skupiny dobře známe: odpovídají trvání proměnných ve standardních programovacích jazycích. Objektovou novinkou je čtvrtá skupina — objekty, které dokáží 'přežít' i ukončení procesu, který s nimi pracuje. V našem seriálu budeme skupiny nazývat následujícími jmény:

  • automatické objekty jsou objekty s obecně nejkratší dobou života (i když v konkrétních případech mohou samozřejmě dynamické objekty existovat kratší dobu) a v neobjektových prostředích jim zhruba odpovídají lokální proměnné. Automatický objekt vznikne na základě požadavku programu; velmi často tento požadavek musí být určen staticky v okamžiku překladu. Automatický objekt — jak jeho jméno naznačuje — zaniká automaticky ve chvíli, kdy program opustí blok, ve kterém byl automatický objekt vytvořen. Objektový systém nemusí podporovat automatické objekty; namísto nich mohou stejně dobře posloužit dynamické. Není-li však součástí systému tzv. garbage collector (viz níže), může být někdy programování v systému bez automatických objektů docela nepohodlné.
  • dynamické objekty jsou základním typem objektů a z hlediska doby trvání jim v neobjektových prostředích nejblíže odpovídají bloky paměti, alokované příkazy malloc, calloc, new a podobně. Vznik i zánik dynamického objektu je vždy výsledkem explicitního požadavku programátora (není-li součástí systému samostatný modul — již zmíněný garbage collector — který může rušit dynamické objekty 'automaticky' rozpozná-li, že je již nikdo nebude potřebovat). Nevyžádá-li si nikdo zrušení dynamického objektu, zanikne objekt nejpozději při ukončení procesu, jehož byl součástí. Bez podpory dynamických objektů se neobejde žádný objektový systém.
  • statické objekty trvají po celou dobu existence procesu a jejich ekvivalentem v neobjektových prostředích jsou globální proměnné. Statický objekt vznikne ve chvíli vytvoření procesu — de facto tedy musí být vytvořen již při překladu — a zaniká vždy ve chvíli zániku procesu. Objektový systém nemusí podporovat práci se statickými objekty; v takovém případě však musí nabízet i neobjektové služby pro prvotní vytváření dynamických objektů. V některých případech může podpora statických objektů také usnadnit programování.
  • trvalé objekty jsou vytvořeny i zrušeny na základě požadavku programátora. Speciálně trvalé objekty 'přežijí' i ukončení procesu který je vytvořil; trvalý objekt, který nikdo nezrušil, bude existovat navěky (přesněji řečeno, po celou dobu existence výpočetního systému, ve kterém trvalý objekt leží). Nejbližším ekvivalentem trvalých objektů v neobjektových prostředích jsou datové soubory. Objektový systém nemusí vůbec podporovat trvalé objekty; ochuzuje tím však programátory o velmi široké možnosti jejich využití.

Pro rozhodnutí o typech objektů, které vývojové prostředí bude podporovat, existují — stejně jako téměř kdekoli jinde— dvě protichůdné tendence: na jednu stranu je výhodné umožnit práci s co nejširší paletou možných typů, aby programátor měl k dispozici flexibilní aparát služeb; na druhou stranu existence řady různých typů objektů komplikuje programátorské rozhraní a zvyšuje pravděpodobnost chyb.

Cocoa proto vůbec nepodporuje automatické objekty (namísto toho však nabízí jednoduchý, ale efektivní poloautomatický garbage collector, který je z programátorského hlediska dokáže více než nahradit). Podpora statických objektů je omezena pouze na třídy (připomeňme, že třídy v Objective C slouží především pro tvorbu nových objektů — musí tedy být samy statické, protože jinak bychom po spuštění programu neměli k dispozici nic, co by objekty dokázalo vytvořit) a na textové objekty, jež významně usnadňují programování.

Textové objekty jsou instance třídy NSString. Důvodem pro podporu statických objektů této třídy je to, že běžný program obsahuje řadu řetězcových konstant; kdybychom neměli k dispozici statické objekty třídy NSString, museli bychom používat konstanty typu char* a 'řetězcové konstanty' vytvářet dynamicky na jejich základě, pomocí výrazů typu:

NSString *slozkaDemo=[NSString stringWithCString:"/NextDeveloper/Demos"];

I kdybychom si pro tento účel připravili makro, pořád by to bylo poměrně nepohodlné a z hlediska běhu programu zbytečně neefektivní. Speciální podpora pro tvorbu statických instancí NSString naproti tomu podobné konstrukce výrazně zjednoduší:

NSString *slozkaDemo=@"/NextDeveloper/Demos";

Nejenže je to výrazně pohodlnější z programátorského hlediska; tento zápis je také daleko efektivnější, neboť daný objekt třídy NSString se vytvoří staticky již při překladu a do paměti je zaveden automaticky při zavádění aplikace.

Pro ostatní třídy podobnou podporu nepotřebujeme, protože jim odpovídající konstanty se používají jen zcela výjimečně (pokud vůbec).

Automatické objekty

Objective C automatické objekty nepodporuje. Díky existenci garbage collectoru však můžeme s dynamickými objekty pracovat přesně stejně, jako by tomu bylo s automatickými:

{ // automatický objekt v C++
  Array cppArray(objekt);
  ...
  // objekt zanikne automaticky při opuštění bloku
}

a odpovídající varianta s dynamickým objektem a využitím garbage collectoru v Objective C:

{

  id array=[NSArray arrayWithObject:objekt];
  ...
  // objekt zanikne automaticky jakmile přestane být zapotřebí
}

Na rozdíl od automatického objektu však je zde nesmírně významný rozdíl mezi "při opuštění bloku" a "až přestane být zapotřebí"; speciálně, v Objective C je naprosto korektní takovýto objekt třeba předat jinému spolupracujícímu objektu, nebo jej vrátit jako návratovou hodnotu:

{ // toto je v Objective C stoprocentně korektní
  id array=[NSArray arrayWithObject:objekt];
  ...
  [jinyObjekt budeTakePracovatSObjektem:array];
  return array;
}

S automatickým objektem by něco podobného bylo možné jen za cenu předávání hodnotou, a to je samozřejmě u objektů, jež mohou obsahovat rozsáhlá data, obecně nežádoucí – nemluvě o takřka neřešitelných problémech se synchronizací hodnot při předávání hodnotou na více míst. V Objective C to funguje zcela korektně i při předávání referencí (připomeňme, že typ id sám je reference, protože není ničím jiným, než ukazatelem na objekt, void*).

Dynamické objekty a garbage collector

Dynamické objekty již vlastně známe: objekt je vytvořen na základě explicitního požadavku, daného odesláním vhodné zprávy některé třídě (viz odstavec "Vznik objektů" výše). Použijeme-li některou z kombinovaných zpráv (typu arrayWithObject: a podobně – zcela obecně, kteroukoli zprávu pro vytvoření nového objektu vyjma alloc/init... a jim zcela ekvivalentního new a ještě vyjma speciálních zpráv copy a mutableCopy, o nichž se zmíníme příště), podléhá nově vytvořený objekt automaticky standardním prostředkům pro správu paměti.

V Cocoa je tímto prostředkem právě již zmíněný poloautomatický garbage collector. Díky jeho existenci se na dynamický objekt standardně musíme dívat spíše jako na automatický (jak jsme si ostatně ukázali v minulém odstavci): objekt bude jistě existovat po celou dobu zpracování aktuální metody, ale potom jej garbage collector může odstranit.

Konkrétně to tedy znamená, že nebudeme-li žádný z objektů, vytvořených pomocí zpráv typu string či arrayWithObject:, odeslaných třídám, potřebovat později, nemusíme se o jeho uvolnění vůbec starat: garbage collector jej uvolní automaticky po ukončení metody, která objekty vytvořila.

V řadě případů je to přesně to, co chceme. Někdy ovšem naopak chceme objekt zachovat déle (třeba až do příštího volání téže metody, nebo až do ukončení aplikace). V takovém případě, nechceme-li, aby objekt byl automaticky odstraněn, musíme to garbage collectoru explicitně sdělit (proto hovoříme o poloautomatickém garbage collectoru – plně automatický by to poznal sám, ovšem za cenu mnoha omezení pro programátora). To uděláme tak, že objektu odešleme zprávu retain: takový objekt pak bude existovat (nejméně) tak dlouho, dokud jej opět neuvolníme.

Obvykle (jakkoli samozřejmě nikoli bez výjimek) tomu tak bývá u objektů, jež se ukládají nikoli do lokálních proměnných, ale do vlastních proměnných objektu – třeba takto:

@interface Foo:NSObject {
  NSString *text;
}
@end
@implementation Foo
-(void)setTextFromFile:(NSString*)filename {
  text=[NSString stringWithContentsOfFile:filename];
  [text retain];
}
...

Nyní je jisté, že textový objekt (obsahující text ze zadaného souboru) bude existovat libovolně dlouho, a nebude garbage collectorem zrušen, dokud to explicitně opět nepovolíme.

Mimochodem, pro větší programátorské pohodlí a lepší ochranu proti chybám a přehlédnutím zpráva retain vždy vrací týž objekt, jemuž byla zaslána (její implementace tedy končí příkazem "return self;"). Díky tomu můžeme namísto dvou výše uvedených řádků implementovat metodu setTextFromFile: lépe pomocí řádku jediného: "text=[[NSString stringWithContentsOfFile:filename] retain];".

Jakmile zjistíme, že objekt již nebudeme potřebovat, uvolníme jej pomocí zprávy autorelease: garbage collector jej pak automaticky zruší po ukončení metody, ve které jsme jej uvolnili. Uvědomme si, že v naší dosavadní implementaci metody setTextFromFile: byla jedna chyba: pokud před jejím voláním proměnná text již nějaký objekt obsahovala, tento objekt již nikdy nebyl přístupný – a také se již nikdy neuvolnil a darmo v programu zabíral paměť. V praxi by tedy správná implementace metody setTextFromFile: nejspíš vypadala takto:

-(void)setTextFromFile:(NSString*)filename {
  [text autorelease]; // uvolníme dosavadní objekt
  text=[[NSString stringWithContentsOfFile:filename] retain];
}

Napoprvé je vhodné si uvědomit, že tato implementace skutečně je správná – i v případě, že žádný dosavadní objekt neexistoval: z dnešního prvého odstavce víme, že nově vytvořený objekt má všechny proměnné nulové, a minule jsme se naučili, že odeslání libovolné zprávy nulovému objektu je zcela korektní prázdná operace: je tedy naše autorelease zcela korektní i tehdy, když text žádný objekt neobsahuje.

Mimochodem, metoda autorelease také, stejně jako retain, vrací self – u ní to však zdaleka nevyužijeme tak často.

Vraťme se ještě na chvíli k rušení objektů: pokud jsme napsali "zruší jej po ukončení metody", neznamená to "zruší jej okamžitě po ukončení metody"; objekt může ve skutečnosti 'přežít' ještě velmi dlouho. Důvod je jednoduchý: dynamické objekty mohou snadno být sdíleny mezi různými moduly nebo různými úseky kódu. S jedním a tímtéž textovým objektem může tedy chtít komunikovat více jiných objektů; každý z nich si nezávisle na ostatních může vyžádat udržení objektu zprávou retain. Garbage collector pak prostě sčítá, kolikrát objekt dostal zprávu retain, a uvolní jej teprve tehdy, když pro každý retain dostal odpovídající zprávu autorelease.

Poloautomatický garbage collector tohoto typu má řadu výhod. Hlavní z nich je, že se nemusíme explicitně starat o uvolnění sdílených objektů — zcela běžnou situací v objektovém prostředí je, že řada objektů spolupracuje s jedním dalším:

Pokud není k dispozici garbage collector, není jasné, který z objektů 1 až 5 má nakonec uvolnit objekt A. Samozřejmě že ten, který jej přestane potřebovat jako poslední; jak to ale v programu zjistit? Tato situace bývá zdrojem častých chyb (kdy si např. objekt 3 myslí že již nikdo nebude objekt A potřebovat a tak jej uvolní, pak se ale na — již neexistující — objekt A obrátí ještě objekt 4 a program se zhroutí); možnost takových chyb garbage collector definitivně odstraňuje.

Garbage collector tohoto typu má také jednu nevýhodu — ilustruje ji

Zde objekt A poslal zprávu retain objektu B, objekt B objektu C, objekt C objektu D a ten zase objektu B. Jinými slovy, objekt A hodlá ještě komunikovat s objektem B, ten s objektem C, ten s D a ten s B. Je zřejmé, že jakmile pošle objekt A objektu B zprávu autorelease, měly by se správně uvolnit všechny tři objekty B, C a D (protože jsou závislé jen samy na sobě a nikdo již je nikdy nebude potřebovat). Garbage collector to ale neví a vědět nemůže — ten pouze zjistí, že každý z objektů B, C a D dostal vícekrát zprávu retain než autorelease a neuvolní ani jeden z nich.

Musíme si tedy dávat pozor, abychom mezi objekty při odesílání zpráv retain nevytvořili 'cyklus', protože garbage collector takový cyklus neumí rozpoznat a objekty nikdy neuvolní.

Nakonec se ještě seznámíme se zprávou release. Zatímco zpráva autorelease řekne garbage collectoru "tento objekt po ukončení této metody nebudu potřebovat", říká zpráva release "tento objekt od této chvíle nebudu potřebovat". Je tedy její použití o něco málo efektivnější, protože objekt se uvolní ihned a neleží v paměti zbytečně po dobu zpracování metody; při jejím používání si však musíme důkladně rozmyslet víme-li opravdu naprosto jistě, že již objekt nebudeme potřebovat. Podívejme se např. na následující úsek kódu, jenž pracuje s vlastní proměnnou objektu currentFont:

...
NSFont *aFont=[text font];
[currentFont release];
currentFont=[aFont retain];
...

Na první pohled se zdá být vše v pořádku — starý font uvolníme (proč ne hned, už jej nikdy potřebovat nebudeme), a místo něj si zapamatujeme aktuální. Přesto toto použití zprávy release může snadno vést k chybě: ono se nám totiž snadno může stát, že minulý font je stejný jako dosavadní! V takovém případě se tento objekt uvolní (nepracuje-li s ním ještě "někdo jiný") hned ve chvíli provedení metody release a zpráva retain se tedy již pošle neexistujícímu — právě uvolněnému — objektu. Použijeme-li však zprávu autorelease, je vše v pořádku — garbage collector by objekt uvolnil až po ukončení metody (ale neuvolní jej, protože objekt mezitím dostal zprávu retain). Proto (a pro řadu dalších podobných háčků a podrazů) lze začátečníkům doporučit, aby využívali takřka výhradně jen zprávu autorelease; zpráva release je vhodná jen pro speciální případy.

Mimochodem, zkušení programátoři si při čtení předcházejících řádků možná řekli: "Aha, standardní počítání odkazů (reference counting), to znám, to je staré, proč OC blábolí o nějakém poloautomatickém garbage collectoru?" Inu, není tomu úplně tak: správa paměti v Cocoa na klasickém a prastarém počítání odkazů skutečně je založena, avšak nabízí toho více: speciálně zpráva autorelease a všechno pohodlí, jež nabízí, v počítání odkazů neexistuje. Klasické počítání odkazů bychom měli v případě, že bychom užívali pro uvolňování objektů výhradně zprávu release, se všemi problémy a se vším nepohodlím, jež by to přineslo.

Úplně nakonec stojí za to se zběžně zmínit o tom, že jediným rozdílem mezi kombinací alloc/init... a odpovídající kombinovanou zprávou jménotřídy... je právě v tom, že ta druhá obsahuje také autorelease. Speciálně tedy např. "[NSArray array]" je přesně totéž, jako "[[[NSArray alloc] init] autorelease]"; podobně třeba "[NSString stringWithContentsOfFile:file]" je totéž, jako "[[[NSString alloc] initWithContentsOfFile:file] autorelease]". Vzhledem k tomu, že zprávy retain a autorelease se (v podstatě) vzájemně ruší, využívá toho řada programátorů tak, že na místě, kde by nově vytvořenému objektu ihned posílali retain, namísto toho použijí kombinaci alloc/init:

-(void)setTextFromFile:(NSString*)filename { // také korektní varianta
  [text autorelease]; // uvolníme dosavadní objekt
  text=[[NSString alloc] initWithContentsOfFile:filename];
}

Statické objekty

Kromě tříd — které jsou všechny standardně statickými objekty — podporuje Objective C pouze statické objekty třídy NSString. Takový objekt vytvoříme zápisem podobné konstanty, jakou určujeme v plain C textový řetězec; před otevírací uvozovkou však umístíme navíc znak '@'. Můžeme využít i automatického spojování více řetězců, jsou-li mezi nimi jen oddělovače – to je velmi šikovné pro zápis dlouhých textových konstant na více řádků:

NSString *foo=@"Text";
if ([foo isEqualToString:@"xyz"]) ...
NSString *bar=@"Toto " @"je " @"jediny " @"string " @"o " @"sedmi " @"slovech.";

Objective C automaticky převede ASCII znaky řetězcové konstanty do vnitřního formátu a vytvoří rovnou při překladu statický objekt třídy NSString s požadovaným obsahem, jenž bude existovat po celou dobu běhu programu. Pozor, skutečně jde o znaky ASCII – ačkoli samy objekty NSString plně podporují Unicode, při zápisu statických textových objektů ve zdrojovém kódu toho nelze využít, a statické objekty obsahující znaky mimo rozsah ASCII nelze vytvářet (lze ovšem využít standardních metaznaků jazyka C, např. \n pro konec řádku či \t pro tabelátor).

Stojí za to se jen zběžně zmínit o tom, že ačkoli třídy jsou statické, knihovny Cocoa nabízejí prostředky jak vytvářet za běhu programu dynamicky nové třídy — ať již prostě zavedením dynamické knihovny, nebo dokonce přímo programově. My si více ukážeme později, až se seznámíme s třídou NSBundle a s jejími možnostmi.

Trvalé objekty

Cocoa standardně umožňuje zapsat prakticky libovolný objekt na disk a opět jej z disku obnovit (přesněji řečeno, Cocoa podporuje zápis objektu a jeho opětovné obnovení prostřednictvím libovolného zařízení — disk zajišťuje trvalé objekty, síť předávání objektů mezi počítači a podobně).

Opět, je třeba neplést si systém trvalých (také někdy říkáme persistentních) objektů s jeho nedokonalou náhražkou, již často nabízejí statické jazyky typu C++, v níž lze pouze obsah objektu zapsat do streamu a naopak nově vytvořený objekt ze streamu inicializovat. Zásadní rozdíl spočívá v tom, že dynamický systém ukládá na zařízení kompletní informace o objektu, včetně jeho třídy; namísto explicitního vytvoření objektu a načtení jeho obsahu tedy prostě řekneme "dej mi objekt", a načte se to, co na zařízení bylo k dispozici — ať je to cokoli.

Zpět k Objective C: základní služby pro práci s persistentními objekty jsou velmi jednoduché:

// vytvoření trvalého objektu
id anyObject=...;
[NSArchiver archiveRootObject:anyObject toFile:@"jméno souboru"];
...
// načtení trvalého objektu
id object=[NSUnarchiver unarchiveObjectWithFile:@"jméno souboru"];
NSLog("Získali jsme objekt třídy %@",[object class]);

Vytváříme-li vlastní třídu objektů, stačí velmi jednoduchým způsobem určit jakým způsobem bude nový objekt kódován a dekódován (s podrobnostmi se seznámíme později, až budeme popisovat třídy NSArchiver a NSUnarchiver). Všechny standardní objekty knihoven Cocoa samozřejmě zápis a obnovení podporují.

Jeden restík od minula...

V minulém dílu jsme si dali malý kvíz, najít nešikovnost v implementaci třídy Angle. Asi je to zbytečné, neboť všem čtenářům je správné řešení určitě už dávno jasné, ale přece jen pro jistotu – samozřejmě, správně to mělo vypadat takto:

@implementation Angle
-(float)radians { return rad; }
-(float)degrees {
  return [self radians]/pi()*180; // využijeme vlastní metodu radians
}
...

Pak stačí v nové třídě přepsat metodu radians tak, aby hodnoty normalizovala, a vše ostatní včetně metody degrees už bude fungovat správně samo, automaticky.

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: