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:
Začínáme s
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):
- Nastal čas na kakao...
- Tak nejdřív kakao ochutnáme...
- Programovací jazyk C: velmi, velmi stručně
- Objective C: to si vysvětlíme podrobněji
- Co jsme si o Objective C ještě neřekli...
- Nastal čas na kakao - Vznik a zánik objektů
- Nastal čas na kakao - Kopírování objektů
- Nastal čas na kakao - Skryté podtřídy
- Nastal čas na kakao - Základní služby objektů
- Nastal čas na kakao - Jak správně psát v Objective C
- Nastal čas na kakao - Jak správně importovat
- Nastal čas na kakao - Podtřídy, delegáti, vkládání, jak se to rýmuje?
- Nastal čas na kakao - Využití kategorií namísto dědičnosti
- Nastal čas na kakao - Vkládání objektů a přesměrování zpráv
- Nastal čas na kakao - Inicializace a rušení objektů
- Nastal čas na kakao - Metody initWith... a designovaný inicializátor
- Nastal čas na kakao - Inicializace: tipy a triky
- Nastal čas na kakao - Accesory: přístup k proměnným instancí
- Nastal čas na kakao - Šedá je teorie, zelený je strom života...
- Nastal čas na kakao - Více o XCode: inspektory
- Nastal čas na kakao - Aplikace RSS2: datový model
- Nastal čas na kakao - Aplikace RSS: implementace datového modelu
- Nastal čas na kakao - Aplikace RSS: parsování XML
- Nastal čas na kakao - Interface Builder a uživatelské rozhraní
- Nastal čas na kakao - Interface Builder: atributy objektů
- Nastal čas na kakao - Interface Builder: atributy objektů
- Nastal čas na kakao - Druhý kontrolér a dokončení aplikace
- Nastal čas na kakao - Drobná vylepšení a zdokonalení...
- Nastal čas na kakao - Ladění
- Nastal čas na kakao - Třídy Foundation Kitu
- Nastal čas na kakao - Třídy Foundation Kitu (2)
- Nastal čas na kakao - Textové řetězce: NS(Mutable)String
- Nastal čas na kakao - Čísla, binární data a další...
- Nastal čas na kakao - Archivace objektů
- Nastal čas na kakao - Trocha magie, aneb distribuované objekty
- Nastal čas na kakao - Málem bychom zapomněli: NSAutoreleasePool
- Nastal čas na kakao - Zpracování výjimek: NSException
- Nastal čas na kakao - NSInvocation a černá magie
- Nastal čas na kakao - Kakao v Tygrovi
- Nastal čas na kakao - Notifikace: nepřímé předávání zpráv
- Nastal čas na kakao - NSUserDefaults
- Nastal čas na kakao - Co nového ve Foundation Kitu
- Nastal čas na kakao – s Intelem, s Intelem, jedeme do...
- Co nového v Xcode
- Začínáme s AppKitem
- Jak MVC v Kakau vypadá doopravdy?
- Jak MVC v Kakau vypadá doopravdy: dokončení
- Přehled tříd AppKitu
- Nastal čas na kakao - Přehled tříd AppKitu 2
- Přehled tříd AppKitu 3: zbývající třídy GUI
- Přehled tříd AppKitu 4: textový systém
- Nastal čas na kakao - Přehled tříd AppKitu 5: hlavně grafika
- Přehled tříd AppKitu 6: dokumentový systém
- Přehled tříd AppKitu 7: dokončení
- Pojmenované vlastnosti objektů
- Pojmenované vlastnosti objektů: implementace
- Pojmenované vlastnosti objektů: relace 1:N
- Pojmenované vlastnosti objektů: řazení jmen a agregační funkce
- Sledování změn objektů
- Sledování změn objektů – ukázka
- Sledování změn objektů – zdrojový kód
- Sledování změn objektů: kód modelu
- Sledování změn objektů: přímý přístup
- Kontroléry a vazby
- Vázání vazeb
- Další vazby s jednoduchým kontrolérem
- Implementace a použití převodu hodnot
- Validace hodnot
- Validace a chyby, a jedna hezká vazba...
- Práce s polem objektů
- Základní vazby NSArrayControlleru
- Převodníky, přepínače, placeholdery
- Mírná vylepšení v mezích zákona
- Objective C 2.0 - novinky z Leoparda
- NSTreeController
- Programování v Cocoa - Pár tipů a triků
- Programování v Cocoa - Základy kreslení
- Kterak nakreslit modrý obdélník...
- Další služby pro kreslení
- Obrázky a písmenka...
- Události a myš
- Lepší práce s myší
- Události klávesnice
- Input Management
- Příkazy a schránka
- Další události
- Táhni a padni
- Byli jsme na tahu; nyní padneme.
- Zvolme si, jak vhodit
- Drobnosti a chybičky
- Speciální případy tahání či házení
- Kterak táhnout něco, co neexistuje?
- Jak na sítě...
- NSURLConnection
- Safari za minutu
- Služby WebKitu
- Kakao v Leopardu
- Druhé Objective C
- Druhé Objective C: různé drobnosti
- Druhé Objective C: kategorie a protokoly
- Druhé Objective C: nový příkaz cyklu
- Druhé Objective C: atributy a accesory
- Druhé Objective C: atributy a accesory
- 64 je dvakrát 32
- Ubicumque dulce est, ibi et acidum invenies...
- Irbis: že prý žádné novinky?
- Blok sem, blok tam, nám už je to všechno jasné...
- Bloky jsou i v AppKitu
- Irbis a Foundation Kit
- Kde jsou má data?
- Kde jsou má data? V NSCache!
- Soubor, jméno, URL, jak se to rýmuje...
- Další podpora NSURL
- Zabíjení!
- A máme tady i...OS!
- Systémové prvky GUI
- Programování pro iOS 1. díl - Rozdíly mezi "i" a "Mac"
- Programování pro iOS - 2. Začínáme programovat
- Programování pro iOS - 3. základní ovladače a propojení GUI s kódem
- Programování pro iOS - 4. Varovná hlášení
- Programování pro iOS - 5. Rámce a jejich řídicí objekty
- Programování pro iOS - 6. Ukládání dat
- Programování pro iOS - 7. Správa paměti a starý restík
- Programování pro iOS - 8. Dokončení aplikace
- Programování pro iOS - 9. Jak dostat aplikaci do iPhone
- Programování pro iOS - 10. Instalace aplikace do cizího iPhone
- Programování pro iOS - 11. Jak dostat aplikaci do libovolného iPhone
- Programování pro iOS - 12. Touching!
- Programování pro iOS - 13. Kreslíme na iPhone
- Programování pro iOS - 14. Udělejme gesto
- Programování pro iOS - 15. Další gesta
- Programování pro iOS - 16. Více prstů, více zábavy
- Programování pro iOS - 17. Podpora standardních gest
- Programování pro iOS - 18. Recognizery v iOS
- Programování pro iOS - 19. Další standardní recognizery
- Programování pro iOS - 20. Co nového v iOSu
- Programování pro iOS - 21. "Multitasking"
- Programování pro iOS - 22. Nulla est honesta avaritia nisi temporis
- Programování pro iOS - 23. Jak se aktivovat, jsme-li v pozadí
- Programování pro iOS - 24. Zbývající drobnosti
- Programování pro iOS - 25. Řídicí objekty rámců
- Programování pro iOS - 26. Jak se dělá UIViewController
- Programování pro iOS - 27. Kde vzít rámce
- Programování pro iOS - 28. Základní služby
- Programování pro iOS - 29. Práce s rámci
- Programování pro iOS - 30. Rotace zařízení
- Programování pro iOS - 31. Správa paměti v rámcích
- Programování pro iOS - 32. Řídicí objekt pro tabulky
- Programování pro iOS - 33. Řídicí objekt pro strom
- Programování pro iOS - 33. Více o UINavigationControlleru
- Programování pro iOS - 35. Ještě jednou UINavigationController
- Programování pro iOS - 36. Po navigátoru taby
- Programování pro iOS - 37. Více o UITabBarControlleru
- Programování pro iOS - 38. Dokončení UITabBarControlleru
- Programování pro iOS - 39. UIPopoverController
- Programování pro iOS - 40. Další triky UIPopoverControlleru
- Programování pro iOS - 41. Zbývající služby UIPopoverControlleru
- Programování pro iOS - 42. UISplitViewController
- Programujeme v
iTunesXcode 4 - Programování pro iOS - 44. Předvolby Xcode 4
- Programování pro iOS - 45. Práce v Xcode 4
- Xcode 4: projekt a cíle
- Xcode 4: práce s cíli
- Xcode 4: Build Settings
- Xcode 4: Build Phases
- Xcode4: Build Phases podruhé
- Xcode 4: Co jsou to Build Rules?
- Xcode4: taje editoru
- Xcode4: automatické doplňování v editoru
- XIBy chyby
- Více o XIBech
- Editor XIBů
- Inspektory pro XIBy
- Vazby mezi objekty v XIBech
- Vazby mezi objekty v kódu
- Paletky Xcode pro XIBy
- Xcode 4: levý sloupec
- Xcode 4: okno Organizer
- Xcode 4: okno Organizer, část druhá
- Xcode 4: co je to Workspace?
- Xcode 4: základy schémat
- Xcode 4: akční schémata
Diskuse k článku
Vložit nový příspěvek Sbalit příspěvky
|
|
|
|
|