OpenStep -- srovnání s Windows API

1. Úvod

Ještě nedávno snad bylo možné se na OpenStep -- dosud jediný standard, popisující objektové API -- dívat jen jako na další krok firmy NeXT, odsouzený -- stejně jako ty předchozí -- i přes špičkovou kvalitu k živoření v nepatrném úseku trhu. Dnes však již je vidět, že tomu tak nebude: kromě vlastních implementací OpenStepu firmy NeXT pro MACH a pro Windows NT je na trhu také OpenStep firmy Sun, který pracuje pod operačním systémem Solaris. Navíc svou budoucnost s OpenStepem spojila také firma Apple, která prostě koupila celou firmu NeXT a na technologii OpenStepu staví svůj budoucí operační systém (známý zatím pod kódovým jménem Rhapsody).

Pokusíme se proto v tomto článku čtenářům velmi stručně OpenStep přiblížit. Protože většina programátorů bude patrně znát důvěrně API MS Windows, použijeme jej zde jako 'odrazový můstek' -- projdeme postupně skupiny služeb Win32 API, a ukážeme si, jak stejnou problematiku řeší OpenStep (bylo by jistě ještě zajímavější ukázat podrobné srovnání službu od služby, ale to bohužel není možné z prostorových důvodů -- již takhle se rozsah článku pohybuje na horní hranici únosnosti). Nejprve se však musíme chvíli věnovat základní otázce: co to vlastně je OpenStep?



1. Co je to OpenStep

OpenStep není software (i když se přeneseně pojem OpenStep pro software často používá); OpenStep je specifikace -- sada dokumentů, popisujících detailně obecné vývojové prostředí, zcela nezávislé na hostitelském operačním systému. V tomto směru jej tedy můžeme srovnat např. s Posixem, nebo s ANSI standardem pro knihovny jazyka C. Vlastní OpenStep má tři základní části, které jsou integrovány do hostitelského systému :

- Display PostScript System je rychlý a efektivní interpret PostScriptu. Součástí OpenStepu jsou navíc nadstavby vyplňující nedostatky 'standardního' PostScriptu -- v OpenStepu např. můžeme pracovat s průsvitnými barvami.

- Foundation Kit je sada hotových tříd, zajišťujících základní programové prostředky pro tvorbu obecných aplikací -- patří sem např. obecné pole dalších objektů, objekt reprezentující textový řetězec, podpora pro persistentní objekty, jednoduchý ale účinný systém správy paměti a podobně. Jeho součástí je i technologie Distributed Objects.

- Application Kit je další sada hotových tříd, tentokrát zaměřená na uživatelské rozhraní aplikací. Nalezneme zde objekty reprezentující okna, dialogy, textové editory...

Standardní součástí vývojového prostředí pro OpenStep je navíc programovací jazyk Objective C, a grafický prostředek pro tvorbu objektové sítě aplikace -- InterfaceBuilder.



1.1. Display PostScript

Zatímco Win32 používají pro grafický výstup nestandardní sadu ad hoc navržených služeb, má programátor v OpenStepu k dispozici kompletní implementaci Display PostScriptu Level 2. Pro ty, kdo znají pouze grafické služby na úrovni "Arc" nebo "LineTo" nabízí PostScript až neuvěřitelně silné služby; jedná se vlastně o samostatný programovací jazyk, optimalizovaný pro tvorbu obrázků. Podívejme se např na obrázek:

ten se celý dá vyrobit třířádkovým prográmkem v PostScriptu:

/Times-Italic findfont 120 scalefont setfont
/printIt {50 50 moveto (OpenStep) show} def
.98 -.02 0 {setgray printIt -1 .5 translate} for

a jediným příkazem v C, kterým PostScript odešleme grafickému serveru. Navíc, jakékoli kvalitní výstupní zařízení, od levných laserových (a dnes již i tryskových) tiskáren až po osvitové jednotky, samozřejmě PostScript umí, takže na výstupu bude přesně to samé, co vidíme na obrazovce -- bez kompromisů.



1.2. Foundation Kit a Application Kit

S některými službami z těchto knihoven se seznámíme níže, v rámci porovnání OpenStepu a Win32. Nejprve si ale musíme uvědomit základní věc -- kompletní služby OpenStepu jsou objektové. Téměř cokoli, s čím programátor pracuje -- textový řetězec, okno, událost, výjimka, ... -- je v OpenStepu objektem. To mimo jiné zjednodušuje programátorské rozhraní -- můžeme například poslat libovolnému objektu zprávu copy: a objekt vytvoří svou kopii. Je samozřejmě naprosto odlišný úkol vytvořit kopii textového řetězce a vytvořit kopii dejme tomu okna na obrazovce; o to se však odesilatel zprávy nemusí starat. Dalším důsledkem je to, že jediný úsek programu může beze změny pracovat s nejrůznějšími objekty -- stačí, jestliže jsou schopny zpracovat stejné zprávy.



1.3. Programovací jazyk Objective C

Programovací jazyk Objective C je na rozdíl od známějšího C++ velmi jednoduchý -- jedná se vlastně o klasické ANSI C, doplněné prostředky pro práci s objekty víceméně převzatými ze SmallTalku. Díky tomu je ale pro objektové programování daleko vhodnější, než v tomto směru poněkud těžkopádné C++ nebo ObjectPascal. Dosavadní implementace OpenStepu navíc využívají jako překladač GNU C, které 'umí' obojí -- jak nadstavbu Objective C, tak jazykové doplňky C++.

Základní a nejvýznamnější novinkou Objective C je nový výraz [<příjemce> <zpráva>], kde příjemce je obecný výraz, jehož výsledkem je libovolný objekt, a zpráva je jméno zprávy, 'proložené' podle potřeby parametry -- podívejme se na několik příkladů:

[aWindow close]; // odešleme oknu zprávu 'close' -- uzavření okna
[anArray objectAtIndex:5]; // získáme 5. objekt v poli

[aString initWithData:dd encoding:NSISOLatin2StringEncoding];

Poslední příklad ukazuje zprávu se dvěma parametry -- ty se v Objective C, stejně jako ve SmallTalku, zapisují 'dovnitř' do jména zprávy za dvojtečky. Díky tomu je program v Objective C obvykle daleko čitelnější než odpovídající aString.initWithDataAndEncoding(dd,NSISOLatin2StringEncoding) v C++.



1.4. Distributed Objects

Systému distribuovaných objektů byl v některém z nedávných čísel věnován podrobný článek; zde si proto jen co nejstručněji vysvětlíme o co jde. Jedná se o systémovou podporu, která umožňuje stejně snadnou komunikaci mezi objekty, které jsou umístěny v různých aplikacích (nebo dokonce na různých počítačích, propojených sítí), jako kdyby objekty byly součástí jediné aplikace.

Obecný systém zpráv, jaký máme v Objective C, je pro distribuované objekty naprosto ideální. Odeslání zprávy je zde totiž velmi abstraktní operace, ani zdaleka se nejedná o prosté 'volání podprogramu', případně 'volání podprogramu nepřímo přes tabulku', jaké známe např. z C++. Proto můžeme snadno používat jedinou operaci zcela univerzálně -- napíšeme-li například

[anArray sortUsingSelector:@selector(compare:)];

setřídí se obsah pole, reprezentovaného proměnnou anArray (každé jeho dva prvky budou pro třídění porovnány odesláním zprávy [prvek1 compare:prvek2]). Pokud ale tuto konstrukci použijeme v programu, nemusíme vůbec vědět, kde pole leží -- uložíme-li do proměnné anArray reprezentaci pole ležícího uvnitř aplikace která příkaz provádí, bude samozřejmě vše fungovat dobře. Systém distribuovaných objektů však zabezpečí, že neméně dobře bude vše fungovat i v případě, že anArray obsahuje identifikaci pole, ležícího ve skutečnosti v počítači našeho přítele v Kalifornii (jsme-li my i on připojeni k Internetu a pokud nám přítel povolil ke svým datům přístup).



1.5. InterfaceBuilder

InterfaceBuilder by byl sám o sobě námětem na samostatný článek; zde se s ním proto můžeme seznámit jen velmi zběžně. Jednou ze základních vlastností objektů OpenStepu je schopnost persistence -- kterýkoli objekt nebo skupinu objektů včetně jejich vzájemných vazeb můžeme 'zakonzervovat' na disk, a až je budeme potřebovat, můžeme je z disku opět načíst.

InterfaceBuilder pak vlastně je grafickým editorem takto uložených objektových sítí. Vzdálenou obdobou InterfaceBuilderu jsou systémy pro tzv. vizuální programování, jako je např. Visual C++; rozdíl ale spočívá v tom, že InterfaceBuilder je plnohodnotným editorem objektových sítí -- není vázán na žádný konkrétní programovací jazyk, a můžeme v něm propojovat jak standardní objekty, tak i objekty tříd, které jsme vytvořili sami nebo které jsou prací třetích firem...

Na obrázku vidíme tradiční příklad: v InterfaceBuilderu jsme použili objekt 'okno' ('Test slideru...'), ve kterém jsou objekty 'textField' a 'slider'. Dva poslední jsme propojili tak, že objekt textField je prostřednictvím 'outletu' target připojen k 'akci' (vlastně zprávě) takeIntValueFrom:. Propojení zajistí, že -- aniž bychom napsali jeden jediný řádek kódu! -- bude jeden objekt přebírat hodnotu druhého. Objekty tedy budou navzájem zobrazovat svůj stav: po zápisu čísla do textového pole se slider nastaví na odpovídající hodnotu a naopak, při tažení slideru se bude měnit obsah textového pole.

Díky promyšlenému návrhu objektů a jejich schopnosti se takto navzájem spojovat lze v OpenStepu obvykle připravit kompletní uživatelské rozhraní aplikace a často i netriviální část sítě objektů, reprezentující data, aniž bychom vůbec začali programovat. To je jednou z příčin, proč příprava aplikace v OpenStepu trvá řádově polovinu až desetinu doby, potřebnou pro vytvoření analogické aplikace pro jiné prostředí.



2. Srovnání Win32 a OpenStepu

Druhou polovinou tajemství obrovské efektivnosti OpenStepu jsou široké a luxusní služby, které programátorům nabízí. Snad všechny běžné úlohy jsou zde pokryty; programátor se tak může soustředit na vlastní aplikaci, aniž by plýtval svým úsilím na řešení již dávno vyřešených problémů.

Ukažme si -- samozřejmě velmi stručně a bez jakéhokoli nároku na úplnost -- některé služby OpenStepu. Namísto podrobného popisu obvykle použijeme příklad; postupovat budeme, jak jsme si slíbili, v pořadí daném službami Win32 API -- vynecháme především ty skupiny služeb, které jsou vázány na hostitelský operační systém (např. Control Panel Applications, File Installation Library, File Systems, Security apod.). Kromě toho bohužel musíme vynechat také služby, které jsou sice obsaženy v obou prostředích, ale nejsou tak zajímavé -- nutí nás k tomu omezený rozsah článku. Protože zajímavost služeb jsem odhadoval sám podle svého nejlepšího vědomí a svědomí, omlouvám se těm, kdo v článku nenaleznou právě tu skupinu služeb, která je enormně zajímá; v takovém případě mi mohou poslat e-mail na adresu ocs@ms.mff.cuni.cz, a já jim stručné informace o tom, jak odpovídající problematiku řeší OpenStep, pošlu obratem.

Zpočátku budeme příklady velmi podrobně komentovat, aby si čtenář mohl zvyknout na Objective C; později budeme stručnější:



2.1. Atoms

Služby pro práci s 'atomy' ve Win32 jsou vlastně triviální sadou služeb pro práci s hashovanou tabulkou textových řetězců. Jsou však omezeny tím, že nemáme k dispozici libovolný počet tabulek -- pouze jednu globální a jednu lokální.

Poměrně přesnou analogií těchto služeb je v OpenStepu třída NSCountedSet -- ta reprezentuje množinu objektů, ve které může být každý objekt umístěn vícekrát. Přitom ale můžeme vytvořit libovolný počet množin (a chceme-li, můžeme kteroukoli z nich sdílet s kteroukoli jinou aplikací nebo aplikacemi; díky distribuovaným objektům však můžeme bez problémů sdílet přímo samotné stringy a nepotřebujeme takové berličky, jakou je ve Win32 globální tabulka atomů), a do množiny můžeme ukládat jakékoli objekty -- nejen textové řetězce. Pro jednoduchost si ale právě příklad s textovými řetězci ukážeme -- provedeme frekvenční analýzu textu (na úrovni slov). O třídách NSCharacterSet a NSScanner se zmíníme později; jejich význam je ostatně jasný z kontextu: NSCharacterSet reprezentuje množinu znaků (zde všechna písmena), NSScaner slouží pro prohledávání textového řetězce:

NSCountedSet *analyse(NSString *str)
// parametrem funkce je textový řetězec
// funkce vrací seznam slov s počtem výskytů
{
NSCountedSet *s=[NSCountedSet set]; // vytvoříme prázdnou množinu
NSCharacterSet *letters=[NSCharacterSet letterCharacterSet]; // písmena
NSScanner *sc=[NSScanner scannerWithString:str]; // procházení řetězce
NSString *word;

while (![sc isAtEnd])
if ([sc scanCharactersFromSet:letters intoString:&word])
[s addObject:[word lowercaseString]];
return set;
}

NSCountedSet jsme použili zatím jen dvakrát -- [NSCountedSet set] vytvoří novou, prázdnou množinu, a [s addObject:x] do dané třídy přidá nový objekt; pokud tam již je, inkrementuje jeho čítač. Ukážeme si výpis informací o získané množině (použijeme standardní službu OpenStepu 'NSLog', která funguje podobně jako printf). Použijeme třídu NSEnumerator, jejíž smysl bude opět jasný z kontextu:

void list(NSCountedSet *s, NSString *w)
// parametrem je množina z min. příkladu
// a slovo, o kterém chceme vědět je-li v množině zastoupeno
{
NSEnumerator *en; // procházení množiny -- viz níže

NSString *x; // aktuální prvek při procházení

NSLog(@"Text obsahoval %d různých slov\n",[s count]);
NSLog(@"Jejich frekvenční tabulka vypadá takto:\n");
for (en=[s objectEnumerator];x=[en nextObject];)
NSLog(@"\t%@ ... %d\n",x,[s countForObject:x]);
if (w)
NSLog(@"Objekt %@ %s prvkem množiny.\n",
w,[s containsObject:w]?"je":"není");
}

Povšimněme si dvou věcí -- předně, textový řetězec, který je parametrem služeb NSLog, je uveden znakem '@'. To je speciální syntaxe OpenStepu, která umožňuje zadat statický řetězcový objekt. Např. @"Ahoj" je tedy objektem (třídy NSString), jehož obsahem je text 'Ahoj'. Kromě toho jsme dvakrát použili neobvyklý konverzní znak '%@'; to v OpenStepu prostě znamená 'vypsat obsah objektu' -- je-li objekt NSStringem, vypíše se odpovídající textový řetězec; jiné objekty vypisují svůj obsah samozřejmě jinak.

Kromě toho OpenStep obsahuje několik dalších tříd, využívajících hashovanou tabulku -- je zde třída NSSet, která může obsahovat každý objekt nanejvýš jednou (nezná tedy počty objektů), ale zato je vysoce optimalizovaná a ještě efektivnější než NSCountedSet. Daleko nejpraktičtější z 'hashovaných' tříd ale je NSDictionary -- obecný slovník, který ukládá dvojice <klíč, hodnota> a umožňuje velmi rychlé vyhledání hodnot podle klíčů. Ukažme si velmi triviální příklad -- útržek kódu, který by mohl sloužit jako vstupní bod např. do BBS systému (pro vstup a výstup proto využijeme standardní ANSI funkce):

char buf[MNOHO];
NSString *login,*name;
NSMutableDictionary *users=[NSMutableDictionary dictionaryWithContentsOfFile:@"Users.dict"];

...
printf("Čau, zadej své login jméno: ");
gets(buf);
if ((name=[users objectForKey:login=[NSString stringWithCString:buf]])==nil) {
printf("\nVítáme nového uživatele... jaxe jmenuješ? ");
gets(buf);
[users setObject:name=[NSString stringWithCString:buf] forKey:login];
}
printf("\nTak nazdar, %s!\n",[name cString]);
...
[users writeToFile:@"Users.dict" atomically:YES];

Kód by měl být v zásadě zřejmý -- jména zpráv v OpenStepu jsou poměrně 'ukecaná', takže význam zprávy je obvykle jasný z jejího anglického jména. Konstanta nil má mezi objekty podobný význam jako konstanta NULL mezi ukazateli -- reprezentuje "žádný objekt". Povšimněme si také převodů mezi C stringy (char *) a objekty třídy NSString... Konečně, volba atomically:YES zajistí, že se při zápisu souboru jeho původní verze nemůže ztratit ani dojde-li např. k zaplnění disku.



2.2. Bitmaps

Pro práci s bitovými mapami slouží v OpenStepu univerzální "obrázková" třída NSImage -- univerzální proto, že dokáže pracovat s obrázky v různých formátech, včetně např. EPS. Obrázek můžeme načíst ze souboru -- OpenStep sám o sobě pro bitmapové obrázky "umí" formát TIFF se všemi potenciálními rozšířeními, včetně interního pakování několika způsoby (mezi kterými je i JPEG) a formát BMP; navíc existují volně šiřitelné doplňky které "naučí" třídu NSImage přímo pracovat s obrázky ve všech možných i nemožných formátech, od CompuServe Graphics přes všechny formáty používané v MS Windows a na počítačích Macintosh až např. po formát PIC počítačů PSION.

Třída NSImage automaticky zajišťuje dlouhou řadu dalších služeb, na jejichž popis zde nemáme místo, jako je např. udržování několika reprezentací obrázku a automatický výběr optimální reprezentace pro dané výstupní zařízení, nebo cache paměť pro urychlení několikanásobného vykreslování složitějších obrázků.

Příklad prostě načte obrázek ze souboru Ryba.tiff (uloženého v aplikaci -- viz popis třídy NSBundle), a zobrazí jej na několika místech:

NSImage *ryba=[NSImage imageNamed:@"Ryba.tiff"];
...
[ryba compositeToPoint:NSMakePoint(10,10) operation:NSCompositeCopy];
[ryba compositeToPoint:NSMakePoint(3,100) operation:NSCompositeSourceOver];
[ryba compositeToPoint:NSMakePoint(30.25,1.12) operation:NSCompositeXOR];

Operace umožňují volit jak se nový obrázek složí s původním pozadím -- nezapomínejme, že v OpenStepu nese kterákoli barva zároveň informaci o průsvitnosti. Zatímco tedy NSCompositeCopy prostě nahradí původní pixely novými, položí NSCompositeSourceOver nový obrázek "přes" staré pozadí -- které skrz něj bude vidět na místech která jsou průsvitná, a do té míry, do jaké jsou průsvitná...



2.3. Brushes, Clipping, Filled Shapes, Lines&Curves...

Všechny skupiny služeb Windows, které souvisí s kreslením, lze v OpenStepu nahradit jediným slovem: PostScript. Jeho schopnosti jsou dostatečně známé; dnes neexistuje žádný jiný standard, který by s PostScriptem snesl alespoň vzdáleně srovnání. Protože nemá smysl zde popisovat PostScript jako takový, ukážeme si, jak s ním obvykle programátor v OpenStepu pracuje.

V nejjednodušších případech můžeme použít hlavní operátory PostScriptu ve formě funkcí. Pak vlastně neprogramujeme v PostScriptu, ale v C, a celý mechanismus se podobá práci ve Win32 API; jediným rozdílem zde je širší nabídka služeb, které PostScript nabízí. Ukažme si triviální příklad, který nakreslí

void drawIt(void)
{
int i;

PSnewpath();
PSmoveto(0,0);
for (i=0;i<6;i++) {
PSrotate(60);
PScurveto(0.4,0.5,-0.4,0.5,0,0);
}
[[NSColor blueColor] set]; // viz odstavec Colors
PSfill();
}

Obvykle však bývá šikovnější napsat potřebné podprogramy přímo v PostScriptu, a pak je z C jen volat. Podrobný popis tohoto mechanismu by přesáhl rámec článku -- pro ty, kdo PostScript znají, můžeme uvést že jde o pswrap. V praxi pak můžeme napsat např. takovýto podprogram v PostScriptu:

defineps textWithStars(char *text; float x,y,size)
8 dict dup begin
/PatternType 1 def /PaintType 1 def
/TilingType 1 def /BBox [0 0 40 40] def
/XStep 40 def /YStep 40 def
/star {

gsave 0 8 moveto 4 {144 rotate 0 8 lineto} repeat
closepath fill grestore
} def
/PaintProc {
begin
0.333 setgray 0 0 40 40 rectfill
1 0 0 setrgbcolor 10 10 translate star
0 1 0 setrgbcolor 0 20 translate star
0 0 1 setrgbcolor 20 -20 translate star
1 1 0 setrgbcolor 0 20 translate star
end
} def
end matrix makepattern setpattern
/Times-BoldItalic findfont size scalefont setfont
x y moveto text show
endps

Tím jsme vytvořili vlastně funkci textWithStars, kterou můžeme volat volně z C:

textWithStars("Kuk",40,180,60);
textWithStars("na",60,120,100);
textWithStars("strejdu!",80,40,140);

výše uvedené příkazy nakreslí

Nebudeme probírat jednotlivé možnosti PostScriptu ve vztahu ke konkrétním službám Win32; pouze se zmíníme o tom, že v OpenStepu je naprosto zbytečné zavádět něco podobného, jak je ve Win32 metafile: PostScript je možné uložit do obyčejného textového souboru (bez ohledu na to zda jej generujeme z programu přímo, nebo prostřednictvím služeb typ PSmoveto), a z něj jej naopak můžeme kdykoli odeslat na libovolné výstupní zařízení -- samozřejmě včetně obrazovky. Nakonec poznamenejme, že ačkoli je OpenStep založen na PostScriptu, a tedy standardně využívá fonty PostScriptové, dokáže pracovat i s fonty TrueType.



2.4. Buttons, Combo Boxes, (Static) Controls, ...

OpenStep obsahuje řadu tříd, které realizují velmi univerzální funkce tlačítek, 'radio buttonů' a mnoha dalších řídících prvků (včetně řady takových, o kterých se Win32 ani nezdá -- viz např. 'color well', popsaný níže). Jen málokdy je vytváříme nebo zpracováváme programově -- obvykle na to slouží InterfaceBuilder, ve kterém můžeme určit i parametry a chování těchto objektů (jako je např. ikona, která má být zobrazena na tlačítku, zvuk, který se přehraje při jeho stisknutí a podobně). Konkrétní vzhled těchto ovladačů záleží na hostitelském prostředí; pravidelní čtenáři Softwarových novin znají vzhled ovladačů OpenStepu/Mach z mých článků, Sun volil pro svůj OpenStep vzhled analogický, v OpenStepu/NT jsou ovladače podobné standardním ovladačům Windows NT, v Rhapsody budou patrně podobné stávajícím ovladačům Macintoshe.

Nemá proto smysl si ukazovat další obrázky z InterfaceBuilderu; namísto toho si stručně ukážeme jak vypadá vazba mezi InterfaceBuilderem a programem. Princip je postaven na tzv. outletech a akcích. Outlet je proměnná, která může obsahovat odkaz na jiný objekt; akce je zpráva, jejímž parametrem je odkaz na objekt (který zprávu odeslal).

Chceme-li pak napsat kód, který bude mít přístup např. k nějakému tlačítku (třeba aby jej mohl podle potřeby zprávou setEnabled: 'zakazovat' a 'povolovat'), nadeklarujeme odpovídající proměnnou:

id tlacitko; // id je typ pro obecný objekt

InterfaceBuilder outlet ve zdrojových textech nalezne a dovolí nám jej propojit čarou (podívejme se na ilustrační obrázek v odstavci o InterfaceBuilderu) s požadovaným tlačítkem. Pak máme jistotu, že až program poběží, bude proměnná obsahovat správnou hodnotu -- rovnou tedy můžeme psát:

if (...) [tlacitko setEnabled:YES];

Potřebujeme-li zajistit i komunikaci v opačném směru -- tj. má-li stisknutí tlačítka volat nějakou službu, kterou musíme sami naprogramovat -- je situace podobně jednoduchá, tentokrát však použijeme akci (zprávu):

-(void)stisknuto:(id)tlacitko;

Nyní můžeme v InterfaceBuilderu propojit kterékoli tlačítko (nebo i jiný objekt, schopný reagovat na akci uživatele) s naším objektem, a v inspektoru Connections (i ten vidíme na zmíněném obrázku) zvolit zprávu stisknuto:. Od té chvíle bude vše fungovat: jakmile uživatel stiskne tlačítko, dostane náš objekt okamžitě zprávu stisknuto:. Jejím parametrem bude stisknuté tlačítko -- toho můžeme využít v případě, že jsme s jednou zprávou propojili tlačítek více:

-(void)stisknuto:(id)tlacitko
{
NSLog(@"Uživatel klepl na tlačítko %@",[tlacitko title]);
}



2.5. Clipboard

OpenStep obsahuje třídu NSPasteboard, která zabezpečuje všechny potřebné služby. Na rozdíl od Win32 v OpenStepu je pasteboardů více -- systém jich standardně spravuje pět: obecný obsahuje jakákoli data (která samozřejmě mohou být ve více formátech). Pasteboardy pro vzhled a styl písma slouží k přenášení těchto informací (tj. jen vzhledu nebo stylu, bez textu) mezi aplikacemi. Vyhledávací pasteboard předává mezi aplikacemi vyhledávaný řetězec -- hledáme-li např. v diskovém správci textový řetězec v souborech, přenese se hledaný řetězec přes pasteboard automaticky do vyhledávacího panelu textového editoru, ve kterém otevíráme nalezené soubory. Konečně poslední pasteboard slouží pro předávání dat v rámci drag&drop. Navíc může každá aplikace -- nebo každá skupina aplikací -- podle potřeby vytvářet a sdílet navzájem libovolné množství dalších pasteboardů.

Ukážeme si úsek kódu, odpovídající službě 'paste' -- tj. převzetí dat z pasteboardu:

NSArray *typy=[NSArray arrayWithObjects:
@"MujGenialniFormat", // nejraději bych měl tento formát
@"MujHorsiFormat", // nebo aspoň tento
NSRTFPboardType, // nebo standardni RTF
NSStringPboardtype, // nebo obyčejný text (UNICODE nebo ASCII)
nil];

NSPasteboard *pb=[NSPasteboard generalPasteboard]; // obecný pasteboard
NSString *typ=[pb availableTypeFromArray:typy]; // nalezení typu (je-li tam)

if (typ) switch ([typy indexOfObject:typ]) {
case 0: useMojeGenialniData([pb dataForType:typ]); break;
case 1: useMojeHorsiData([pb dataForType:typ]); break;
case 2: useRTFData([pb dataForType:typ]); break;
case 3: useStringData([pb dataForType:typ]); break;
}

kde useXXXData jsou naše vlastní funkce, které data, převzatá z pasteboardu, zpracují. Opačný případ -- zápis dat na pasteboard -- je ještě jednodušší:

[pb declareTypes:typy owner:self]; // 'pb' a 'typy' z minulého příkladu
[pb setData:getMojeGenialniData() forType:@"MujGenialniFormat"];

a to je vše -- ostatním aplikacím jsou nyní k dispozici na pasteboardu data ve všech čtyřech formátech. Jak je to možné, když jsme tam zapsali pouze jeden? Snadno -- OpenStep šetří čas a paměť při ukládání většího množství formátů právě tím, že umožňuje aplikaci, aby na pasteboard zapsala jen některé, nebo třeba žádný z formátů určených ve zprávě declareTypes. Jestliže pak bude někdo chtít převzít z pasteboardu data, která na něm zatím fakticky nejsou (nebo automaticky před ukončením aplikace), dostane objekt 'owner' zprávu pasteboard:provideDataFortype:, v rámci které může 'opomenutí' napravit -- ovšem teprve tehdy když to je zapotřebí, a ne zbytečně.



2.6. Colors

Oproti standardnímu PostScriptu můžeme v OpenStepu navíc pracovat s průsvitností. Každá barva je určena jak vlastním barevným tónem (vyjádřeným podle potřeby v modelu RGB, CMYK, HSB nebo v některém z modelů s pojmenovanými barvami, jako je např. PANTONE), tak i mírou průsvitnosti (tzv. alfa). Míra průsvitnosti je -- stejně jako ostatní barevné složky -- určena libovolným desetinným číslem mezi 0 a 1 -- nula znamená zcela čirou barvu, 1 zcela neprůhlednou.

Součástí API je několik tříd: NSColor reprezentuje vlastní barvu, nastavuje ji jako aktivní barvu pro kreslení, převádí mezi různými barevnými modely a podobně. NSColorList spravuje pojmenované seznamy barev (PANTONE). O třídě NSColorPanel se zmíníme v příštím odstavci; k ní patří třída NSColorPicker, která usnadňuje tvorbu specifických mechanismů pro výběr barvy. Konečně NSColorWell je prvkem UI (patří tedy vlastně mezi tlačítka, textová pole a podobně), a jedná se o reprezentaci barvy -- vlastně barevné políčko, na které a ze kterého můžeme předávat barvu mechanismem drag&drop, a ze kterého můžeme otevřít panel pro výběr barvy.



2.7. Common Dialog Box Library, Dialog Boxes, File Manager Extensions

OpenStep nezná speciální prvek 'dialogový panel'. Namísto toho je možné libovolné dialogové prvky (tlačítka, textová pole, ...) umístit do kteréhokoli okna; je čistě věcí programátora která okna pak budou sloužit jako 'panely', a která jako 'tradiční okna', a která budou kombinovat služby obou...

Je-li zapotřebí využít tzv. modální panel (tj. takový, který je nutno uzavřít dříve, než aplikace může pokračovat), můžeme využít zprávu runModalForWindow:, která zajistí modální běh libovolného okna. To znamená, že toto okno bude dostávat všechny události generované klávesnicí nebo myší, dokud službu (obvykle v rámci stisknutí tlačítka OK nebo Cancel v okně) neukončíme.

V praxi to však je jen málokdy zapotřebí, protože z hlediska uživatele je žádoucí, aby pokud možno co největší počet panelů byl nemodální (tj. aby uživatel mohl s aplikací pracovat i v době, kdy je panel zobrazen). Nadto jsou pro nejběžnější modální panely k dispozici hotové příkazy -- ukažme si dva z nich na úseku programu, který nejprve zobrazí standardní panel pro výběr souboru, a pak se vybrané soubory pokusí zpracovat. Pokud to nejde, zobrazí 'attention panel' s odpovídajícím upozorněním:

NSOpenPanel *op=[NSOpenPanel openPanel];

// nil zde znamená "libovolný soubor":
if ([op runModalForTypes:nil]==NSOKButton) {
// uživatel může vybrat více souborů najednou!
NSEnumerator *en=[[op filenames] objectEnumerator];
NSString *file;

while (file=[en nextObject])
if (!zpracujSouborAVratYesPriUspechu(file))
NSRunAlertPanel(@"Chyba",@"Nelze zpracovat soubor %@",nil,nil,nil,file);
}

OpenStep nabízí řadu dalších standardních panelů, modálních i nemodálních -- je zde např. panel pro nastavení parametrů tisku i vzhledu tištěného dokumentu, panel pro volbu barvy, panely pro inspekci sdílených dat (analogie OLE, resp. DDE) a další... Většina z nich přitom umožňuje připojení uživatelem definovaného objektu -- můžeme tak snadno panel rozšířit o libovolné vlastní ovladače.



2.8. Data Decompression Library, Device I/O, Shell Library

Pro programové vyvolání takovýchto služeb nabízí OpenStep tzv. Workspace protokol. Ukažme si některé jeho služby:

NSWorkspace *w=[NSWorkspace sharedWorkspace];
// w reprezentuje pro program systémový 'file manager'

[w openFile:@"/Docs/Příručka.rtf"];
// totéž, jako kdybychom na soubor 'Příručka.rtf'
// poklepali myší v systému
NSImage *i=[w iconForFileType:@"tiff"];
// vrátí standardní ikonu pro soubory *.tiff
[w performFileOperation:NSWorkspaceCopyOperation
source:@"/Docs"
destination:@"/ArchivedDocs"
files:[NSArray arrayWithObject:@"Příručka.rtf"]
tag:&tag];

Poslední příklad zkopíruje soubor '/Docs/Příručka.rtf' do adresáře '/ArchivedDocs'; 'files' jsou zadány jako NSArray proto, že kopírovaných souborů (nebo adresářů) může být více najednou. Operace je určena také proměnnou (NSWorkspaceCopyOperation) proto, že operací je k dispozici více -- kromě kopírování můžeme soubory přemísťovat nebo vytvářet linky, můžeme soubory rušit, kódovat a dekódovat, komprimovat a dekomprimovat a tak dále. Operace je asynchronní (tj. program si operaci vyžádá a ihned pokračuje v práci, zatímco operační systém ji provádí); proto je zde proměnná tag, která slouží k identifikaci operace v případě, že chceme např. vyčkat jejího ukončení.

Workspace protokol obsahuje řadu dalších služeb např. pro práci s výměnnými médii, pro zjišťování na kterém médiu je zadaný soubor a podobně. Jen pro zajímavost -- je zde např. také služba slideImage:from:to:, která umožňuje programu pohodlně animovat zpracování objektů (reprezentovaných ikonami) na obrazovce...

Win32 API obsahuje ve společné skupině s některými z výše popsaných služeb i podporu pro drag&drop. Ta je v OpenStepu zajištěna poměrně jednoduše: výchozí objekt zvolí ikonu a parametry pro tažení; objekty 'po cestě' jsou pak informovány, a mohou na tažení patřičně reagovat. Objekt nad kterým je tažený objekt puštěn pak získá přímé datové propojení na objekt, který tažení zahájil (k tomu slouží pasteboard, se kterým jsme se již seznámili). OpenStep navíc nabízí řadu zpráv pro usnadnění standardních případů -- např. pomocí zprávy dragFile: můžeme zajistit tažení daného souboru (reprezentovaného jeho vlastní ikonou), aniž bychom museli vyplňovat všechny odpovídající parametry sami.



2.9. Edit Controls

Pro veškerou práci s textem -- ať již se jedná o komplikovaný editor, nebo třeba o titulek okna -- se v OpenStepu využívá textový systém, vystavěný kolem třídy NSText (a mnoha dalších podpůrných tříd, se kterými se však běžný programátor, který nevyžaduje speciální služby, vůbec nemusí seznámit).

Základem celého systému jsou vlastní texty, reprezentované objekty třídy NSString -- díky tomu celý textový systém obsahuje všechny standardní služby NSStringů (se kterými se ještě seznámíme, a které jsou skutečně luxusní), a díky tomu je také schopen pracovat v UNICODE. Nad nimi je vytvořena třída NSAttributedString, která doplňuje zcela obecné atributy -- lze v ní vyjádřit vše potřebné, od tučného písma nebo kurzívy, přes zarovnání nebo odsazení odstavců až po kerning, barvu písma nebo zcela libovolné uživatelem definované atributy. Konečně nad NSAttributedStringem stojí vlastní třída NSText; ta zajišťuje zobrazení a editaci textů. Přitom buď zcela automaticky (díky spolupráci objektů), nebo jen se zcela minimální podporou programátora nabízí mj.:

- zobrazení a využití 'ruleru' pro určení tabelátorů, odsazení, řádkování a podobně;
- spolupráci s panely a nabídkami pro výběr fontu, stylu, vzhledu a barvy písma;
- spolupráci s pasteboardem (cut/copy/paste);
- vkládání libovolných objektů do textu pomocí drag&drop;
- zobrazení jednoho textu v několika různých oknech;
- rozdělení textu na stránky, sloupce a podobně;
- psaní textu v libovolném směru;
- zobrazení textů v prostoru s libovolně složitou hranicí (speciálně tedy obecné obtékání obrázků);
- všechny běžné atributy, včetně kerningu;
- automatické využití ligatur pro odpovídající kombinace písmen (fi, fl a podobně -- podle jazyka a jeho zvyklostí).

Příklad tentokrát nebudeme uvádět, protože na složitější příklad nemáme dostatek místa, zatímco jednodušší by neobsahoval žádný program -- automatická spolupráce objektů v OpenStepu je u textových objektů dotažena do dokonalosti, takže textový editor se schopnostmi daleko přesahujícími např. T602 vytvoříme v samotném InterfaceBuilderu, aniž bychom museli napsat více než pár řádků programu...



2.10. Error, Structured Exception Handling

OpenStep vůbec nepodporuje nějaké vracení chybových kódů -- v systému, který podporuje výjimky, by to také byla nekonsistentní hloupost (které se Windows dopouštějí patrně kvůli svému DOSovskému dědictví). Systém výjimek je jednoduchý -- na místě klíčových slov try/except stojí v OpenStepu třída NSException a několik maker. Ukažme si rovnou příklad použití -- věřím, že těm, kdo znají mechanismus try/except, bude vše ihned jasné:

void fnc(int i)
{
fncA();
...
if (/*error*/)
[NSException raise:@"Fnc" format:@"parametr=%d",i];
... // došlo-li k chybě, tento kód se neprovede
}
void main()
{
int i;

NS_DURING
...
for (i=0;i<X;i++) fnc(i);
...
if (/*error 1*/)
[NSException raise:@"Main" format:@"error 1"];
... // došlo-li k chybě, tento kód se neprovede
NS_HANDLER
NSLog(@"Chyba v %@, %@",[exception name],[exception reason]);
NS_ENDHANDLER
}

Důležité je, že celý systém by pracoval korektně i kdyby na místě funkce fnc byla objektová zpráva, a to i v případě, že by odešla prostřednictvím distribuovaných objektů do jiné aplikace! Poznamenejme také, že do objektu NSException můžeme uložit libovolná data, specifikující blíže příčinu chyby -- nejsme tedy omezeni jen na jméno a formát, které využívá uvedený příklad.

Hlášení chyb prostřednictvím třídy NSException samozřejmě využívají i všechny třídy OpenStepu, takže např. program

void main()
{
NSArray *a=[NSArray arrayWithObjects:@"A",@"B",nil];

NS_DURING
[a objectAtIndex:3];
NS_HANDLER
NSLog(@"Chyba %@ %@\n",[exception name],[exception reason]);
NS_ENDHANDLER
}

vypíše "Chyba NSRangeException *** objectAtIndex:: index (3) beyond bounds (2)".



2.11. Files, File Maping, Communication

Pro práci se soubory nabízí OpenStep řadu služeb. Předně, kterýkoli objekt, který může obsahovat data (NSString, NSData, NSImage,...) může být vytvořen načtením ze souboru, a naopak, dokáže svůj obsah do zadaného souboru zapsat (u NSStringu navíc můžeme volit má-li se použít UNICODE, nebo má-li se obsah nejprve zkonvertovat do některého z osmibitových encodingů, u NSImage vybíráme případnou vnitřní kompresi obrázku a podobně). Hlavním prostředkem pro práci se soubory však je třída NSFileManager, která umožňuje procházení systému souborů, vytváření a rušení souborů a adresářů, nabízí přístup k obsahu souborů a tak dále. Podstatné přitom je, že třída zajišťuje (ve spolupráci s některými službami třídy NSString, se kterými se ještě seznámíme) korektní práci se systémem souborů, aniž bychom museli znát takové věci jako jsou oddělovače jmen cest, souborů a typů, nebo jako je kódování jmen souborů.



2.12. High&Low Level Audio...

Z nějakého důvodu není (kromě triviální služby NSBeep) podpora práce se zvukem přímo součástí OpenStepu. Existující implementace (NeXT, Sun) však obsahují tzv. Sound Kit -- plně objektovou podporu práce se samplovanými zvuky, s využitím několika možností online komprese a s víceméně libovolnou samplovací frekvencí, velikostí samplu a počtem kanálů. Analogicky tomu je s podporou videoklipů -- na úrovni OpenStepu není požadována, ačkoli existující implementace pro ni obsahují NeXTTIME Kit.



2.13. Keyboard Input, Messages and Messaage Queues, Mouse Input

Je roztomilé, tvrdí-li dokumentace Win32 API, že obsahuje podporu vstupu na základě událostí, "unlike traditional environments". Tedy, to co nabízí Win32, je nejtradičnější přístup k událostem, jaký si umím představit; OpenStep naproti tomu nabízí následující mechanismus:

Základem je samozřejmě klasický event loop; ten je však před programátorem ukryt uvnitř třídy NSApplication. Ta sama přebírá a zpracovává události, aniž by se o to programátor musel jakkoli starat, a zajišťuje jejich předávání patřičným objektům:

- stisknutí klávesy dostane vždy objekt, ve kterém je kurzor (nebo jeho ekvivalent pro netextové objekty), jako zprávu Objective C keyDown:
- stisknutí klávesy na myši dostane automaticky ten objekt, nad kterým byl v okamžiku stisku kurzor myši, jako zprávu mouseDown:
- informaci o příchodu znaku na vstupní rozhraní apod. dostane ten objekt, který si ji vyžádal.

Analogicky je řešeno zpracování ostatních událostí. Situace je vzdáleně podobná programování např. ve Visual BASICu: vytváříme-li objekt, který má nějak reagovat na stisknutí klávesy myši, prostě implementujeme metodu mouseDown: -- o vše ostatní se postará korektně systém. Je-li to ve speciálních případech zapotřebí, má nicméně programátor samozřejmě k dispozici plnou kontrolu nad událostmi (které jsou samy reprezentovány objekty NSEvent) prostřednictvím služby getEvent. Ukažme si triviální příklad -- chceme vytvořit objekt, který po stisknutí myší zabrání jakékoli další aktivitě dokud tlačítko myši opět neuvolníme. Stačí implementovat zprávu mouseDown: na úrovni daného objektu takto:

-(void)mouseDown:(NSEvent*)evt
{
[self showBusyState:YES]; // visualni odezva na stisknuti
while ([evt type]!=NSLeftMouseUp)
evt=[[self window] nextEventMatchingMask:NSLeftMouseUpMask];
[self showBusyState:NO];
}



2.14. Memory Management

Klasická správa paměti je v objektovém prostředí zhola zbytečná; určitou formou správy paměti však OpenStep disponuje: každý objekt -- podobně jako paměťový blok -- je vytvořen, používán, a nakonec uvolněn. OpenStep obsahuje sadu prostředků, které zajišťují uvolnění objektu teprve ve chvíli, kdy jej již nikdo nepotřebuje (tzv. garbage collector); tyto prostředky pracují poloautomaticky -- tj. programátor musí správu paměti aktivně podpořit, zato však pracují korektně i při sdílení objektů mezi více aplikacemi (což by plně automatický garbage collector nedokázal).



2.15. Registry and Initialization Files

Předvolby jsou v OpenStepu uloženy jako dvojice <klíč, hodnota> -- např. <BarvaTextuVHlavnímOkně, Black>. Dvojice jsou uloženy v tzv. doménách. Systém uživatelských předvoleb pak spravuje libovolný počet pojmenovaných domén v předem zadaném pořadí; chceme-li vyhledat hodnotu k zadanému klíči, prohledávají se postupně domény podle jejich pořadí. Standardně je domén pět: argumentová (příkazový řádek), aplikační (specifické pro tu kterou aplikaci), globální (pro všechny aplikace), jazyková (předvolby závislé na aktivním jazyce) a registrační. Tu si vytváří aplikace při spuštění, a jejím účelem je doplnit 'nejdefaultnější' hodnoty pro případ, že v žádné jiné doméně nebudou:

NSUserDefaults *def;

// následující metoda je automaticky volána ihned
// po startu aplikace
-(void)applicationDidFinishLaunching:notification
{
id reg=[NSDictionary dictionaryWithObjectsAndKeys:
@"12",@"Počet",
@"YES",@"JeToTak?",
@"Pepa z depa",@"Jméno",
nil];
def=[NSUserDefaults standardUserDefaults];
[def registerDefaults:reg]; // vytvoření registrační domény
....
}

Domény aplikační, globální a jazykové (těch je více, a standardně se vždy použije ta, která odpovídá uživatelem zvolenému jazyku) jsou uloženy v systémové databázi, kterou systém spravuje zvlášť pro každého uživatele. Kdekoli v aplikaci pak můžeme volně používat standardní hodnoty -- např. takto:

NSLog(@"Počet je %d",[def integerForKey:@"Počet"]);
NSLog([def boolForKey:@"JeToTak?"]?@"Je to pravda":@"Nejni to pravda!");
NSLog(@"Jméno: %@",[def stringForKey:@"Jméno"]);
if (o=[def objectForKey:@"Tralala"]) ...
else NSLog(@"Default \"Tralala\" neexistuje");

Jakmile pošleme objektu 'def' např. zprávu integerForKey:@"Počet", začnou se prohledávat domény uživatele, který aplikaci spustil, v pořadí daném pořadím domén uložených v objektu 'def'. Standardně se tedy nejprve zjistí, nebyl-li při spuštění aplikace použit parametr "Počet=n" (argumentová doména), pak se klíč "Počet" postupně hledá v aplikační doméně, v doméně globální a v doménách jazykových. Jestliže není nalezen v žádné z nich, použije se nakonec hodnota 12 z domény registrační, do které byla uložena při startu aplikace.

Aplikace samozřejmě může měnit obsah své aplikační domény, může vytvářet nové domény podle potřeby, může měnit standardní seznam prohledávaných domén, má přístup k předvolbám jiných uživatelů a tak dále...



2.16. Resources

V systému, který umožňuje editaci hotových objektových sítí (InterfaceBuilder), jsou klasické resourcy zbytečné -- nemá smysl zavádět systém pro editaci dat, na základě kterých by se poloautomaticky vytvářel např. objekt 'okno', když můžeme editovat přímo tento objekt.

Jedinou částí klasických resourců nepokrytou editací objektových sítí tak jsou tabulky lokalizovaných textových řetězců, používaných přímo v programu. To však řeší OpenStep daleko pohodlnějším způsobem -- na místě stringu použijeme funkci NSString *NSLocalizedString(NSString *key, NSString *comment); ta zajistí vyhledání správného řetězce v tabulkách pro aktivní jazyk:

if (...) NSLog(NSLocalizedString(@"Error",@"error report"));

Tento úsek programu vypíše text "Error" je-li zvolena angličtina, text "Chyba" je-li zvolena čeština a tak dále -- jsou-li samozřejmě k dispozici správné překladové tabulky. Ty dokáže OpenStep generovat automaticky, a původní (anglické) znění v nich doplní právě poznámkou. Překladatel tedy dostane tabulku, která bude vypadat přibližně takto:

...
/* error report */
"Error" = ""
...

do které pouze doplní správné znění textu v příslušném jazyce. OpenStep pak automaticky vždy použije správnou překladovou tabulku; hned si ukážeme, jak je to možné. Klíčem k těmto službám je struktura aplikace v OpenStepu a třída NSBundle. Aplikace je v OpenStepu totiž reprezentována adresářem, který obsahuje jak vlastní program, tak i všechny pomocné soubory (v podadresáři Resources). Jméno aplikačního adresáře můžeme zjistit takto:

// tzv. main bundle reprezentuje adresář aplikace:
NSBundle *b=[NSBundle mainBundle];
NSLog(@"Aplikace je v adresáři %@",[b path]);

NSBundle umí vyhledávat také pomocné soubory, uložené uvnitř aplikace -- např. soubor "ÚvodníText.ascii" vyhledáme takto:

NSString *fname=[b pathForResource:@"ÚvodníText" ofType:@"ascii"];

Celá lokalizační finta spočívá v tom, že pomocné soubory mohou být uloženy nejen v "Resources", ale také v podadresářích <jméno jazyka>.lproj (např. English.lproj, Czech.lproj). Takových podadresářů může aplikace obsahovat libovolně mnoho -- jeden pro každý jazyk, ve kterém může pracovat. Uživatel pak zvolí vlastní pořadí jazyků:

a třída NSBundle hledá soubory v 'jazykových' podadresářích, a ty prohledává v pořadí daném předvolbou uživatele. Minulý příklad tedy při nastavení, které ukazuje obrázek, vrátí jméno "...aplikace../Resources/Czech.lproj/ÚvodníText.ascii"; pokud by však uživatel změnil pořadí a umístil francouzštinu na začátek, nalezl by se soubor  "...aplikace../Resources/French.lproj/ÚvodníText.ascii". Pokud soubor není uložen na žádném z jazykových adresářů, může být přímo přímo v adresáři Resources -- to umožňuje stejnou službou vyhledávat jak lokalizované, tak i univerzální soubory. Stejným způsobem se vyhledávají soubory, obsahující tabulky přeložených textů, o kterých jsme psali na začátku tohoto odstavce.



2.17. String Manipulation and UNICODE

Ukažme si rovnou jak řetězce vytvářet:

// řetězec lze načíst přímo ze souboru:
id a=[NSString stringWithContentsOfFile:@"/tmp/something.text"];
// nebo vytvořit pomocí "printf"-formátu:
int n=15;
id b=[NSString stringWithFormat:@"%d soubor%s",n,n>4?"ů":n>1?"y":""];

a co se s nimi dá dělat (NSRange je struktura, obsahující pozici a délku):

[b stringByAppendingString:b] // "15 souborů15 souborů"
[b stringByAppendingFormat:@" je prostě %@",b] // "15 souborů je prostě 15 souborů"
[b substringFromIndex:3] // "souborů"
c=[b mutableCopy]; // získáme kopii, jejíž obsah můžeme měnit

[c deleteCharactersInRange:NSMakeRange(4,2)]; // c="15 sborů"
[c appendString:@" zpívá"]; // c="15 sborů zpívá"
[c insertString:@"ne" atIndex:9]; // c="15 sborů nezpívá"

porovnávání a vyhledávání:

[b hasPrefix:@"15"] // YES
[c hasSuffix:@"pívá"] // YES
[b isEqual:c] // NO
NSRange r=[c rangeOfString:@"bor"];
NSLog(@"\"bor\" je na pozici %d, před ním je \"%@\"",
r.location,[c substringToIndex:r.location]);
// vypíše ""bor" je na pozici 4, před ním je "15 s""

Lze hledat odzadu a/nebo bez ohledu na velikost písmen; prohledávaný rozsah můžeme omezit. Zajímavá je i možnost vyžádat si nejdelší společný prefix dvou řetězců:

[b commonPrefixWithString:c options:NSCaseInsensitiveSearch] // "15 s"

NSString plně podporuje UNICODE (a umí samozřejmě UNICODE řetězec i načíst ze souboru nebo jej do něj zapsat). V praxi se zatím ale více využívají osmibitová kódování; OpenStep proto nabízí služby:

// vypíšeme všechna kódování, která jsou k dispozici
NSStringEncoding *en=[NSString availableStringEncodings];
while (en)
NSLog(@"%@",[NSString localizedNameOfStringEncoding:en++]);
// zjistíme které kódování odpovídá běžným Céčkovým řetězcům
en=[NSString defaultCStringEncoding];
// ověříme lze-li do něj převést string b bez ztráty informace
if ([b canBeConvertedToEncoding:en]) // a ano-li, převedeme jej:
newb=[b dataUsingEncoding:en];
else { // ne-li, vyhledáme nejúspornější bezztrátové kódování
en=[b smallestEncoding]; // a použijeme jej:
newb=[b dataUsingEncoding:en];
}
// a ještě b převedeme přímo do ISO-8859/2:
isol2b=[b dataUsingEncoding:NSISOLatin2StringEncoding];

Nakonec si ukážeme pár služeb pro práci s názvy souborů a adresářů -- programátor se v OpenStepu nemusí starat, zda se oddělují jednotlivé položky lomítkem, obráceným lomítkem nebo třeba dvojtečkou:

// příklady buďtež z Unixu:
NSString *p=@"/Users/oc/Apps/Test.app";
[p lastPathComponent] // "Test.app"
[p pathExtension] // "app"
[p stringByAppendingPathComponent:@"Resources"] // "/Users/oc/Apps/Test.app/Resources"
[p stringByDeletingLastPathComponent] // "/Users/oc/Apps"
[p stringByAbbreviatingWithTildeInPath] // pro uživatele "oc": "~/Apps/Test.app"
NSString *p=@"~/Apps/Test.app";
[p stringByExpandingTildeInPath] // pro uživatele Steve: "/Users/Steve/Apps/Test.app"

NSString toho nabízí více -- můžeme dokonce fakticky prohledávat disk a získat seznam všech souborů, odpovídajících zadané specifikaci...

Pro složitější zpracování řetězců slouží pomocné třídy NSScanner a NSCharacterSet. Příklad byl na začátku u třídy NSCountedSet -- ukážeme si ještě jeden:

NSString *s=@"12 chlapů na mrtvého bedně, Johoho!";
NSScanner *sc=[NSScanner scannerWithString:s];
int i;
NSString *s1,*s2;

[sc scanInt:&i]; // načteme číslo
[sc scanCharactersFromSet: // načteme text dokud jsou v něm...
[NSCharacterSet letterCharacterSet] // ... jen písmena
intoString:&s1];
[sc scanUpToCharactersFromSet: // přeskočíme vše až k nejbližšímu...
[NSCharacterSet uppercaseLetterCharacterSet] // ... velkému písmenu
intoString:NULL];
[sc scanUpToString:@"ho" // načteme vše dokud nenarazíme na "ho"
intoString:&s2];
NSLog(@"%d, \"%@\", \"%@\"",i,s1,s2); // vypíše "12, "chlapů", "Jo""



2.18. Timers

Nejjednodušší 'timerovou' službu nabízí sama třída NSObject (a díky dědičnosti ji máme k dispozici pro libovolný objekt OpenStepu). Kterémukoli objektu proto můžeme odeslat zprávu

[obj perform:@selector(msg:) withObject:o2 afterDelay:ms];

systém pak zabezpečí, že až uplyne ms milisekund, dostane objekt obj zprávu msg:o2. Máme-li náročnější požadavky, můžeme využít třídu NSTimer, která dokáže odesílat zprávy podle potřeby opakovaně a s větší flexibilitou.



2.19. Vynechali jsme snad něco?

Samozřejmě, vzhledem k omezenému rozsahu článku jsme vynechali řadu skupin -- patří sem např. služby Win32 pro práci s kurzorem a jim odpovídající třída OpenStepu NSCursor; služby pro obsluhu pipes a třída NSPipe, služby pro mapování souborů do paměti a pro komunikaci a třída NSFileHandle, která pokrývá obojí; ListBox z Win32, a třídy NSTableView a NSBrowser, z nichž každá dokáže při nejtriviálnějším možném použití totéž co ListBox... snad bude ještě někdy příležitost podívat se na vše v samostatném článku podrobněji. Nyní však je důležitější si ukázat, jaké služby nabízí Win32 API, ale nenajdeme je v OpenStepu:

Samozřejmě, všechny skupiny služeb úzce vázaných na konkrétní operační systém -- ty v univerzálním přenositelném API být ani nemohou;
Consoles and Character Mode: OpenStep jako grafický systém samozřejmě nenabízí řádkové rozhraní;
High&Low level Audio, Multimedia: OpenStep multimedia (kromě obrázků) přímo nepodporuje, a ponechává odpovídající služby na externích kitech (Sound Kit, NeXTTIME Kit). To samozřejmě znamená, že u aplikací, které tyto služby využijí, není zaručeno, že budou pracovat pod všemi implementacemi OpenStepu.

Naopak, OpenStep nabízí řadu dalších služeb, které ve Win32 nenalezneme. Zmínili jsme se již např. o persistentních objektech (tj. možnosti kterýkoli objekt nebo síť objektů i s jejich vzájemnými vazbami uložit na disk); Win32 tuto službu z principu nabízet nemůže, protože se nejedná o objektové prostředí. Přitom je to velmi silný prostředek, umožňující mj. prakticky zadarmo implementovat ukládání a načítání velmi komplikovaných dokumentů... Zajímavá třída je třeba NSNotificationCenter; ta dokáže zajistit předávání zpráv i v případech, kdy odesílající neví, které objekty mají zprávu dostat (vědí to ale ony, a přijímání takových zpráv si předem vyžádaly). OpenStep standardně obsahuje třídy které zajišťují služby korektoru pravopisu (spellcheckeru), a které umožňují připojit korektory pro další jazyky (např. český je komerčně k dispozici). Tak bychom mohli pokračovat ještě dlouho; bohužel však musíme případného zájemce odkázat na některou příručku OpenStepu.



3. Závěrem

Ačkoli článek byl dost a dost dlouhý, podařilo se nám přesto zahlédnout jen špičku ledovce. O řadě důležitých koncepcí OpenStepu, jakými jsou např. protokoly, systém skrytých podtříd nebo koncept měnitelných a neměnných objektů jsme se vůbec nezmínili. Také naprostá většina služeb a řada méně důležitých tříd zůstaly utajeny. Nezbylo bohužel místo ani na podrobnější ukázku vývoje aplikace s využitím InterfaceBuilderu... Přesto doufám, že hlavní úkol -- to jest, dát programátorům ve Win32 API orientační představu o tom, co je čeká rozhodnou-li se využívat OpenStep -- snad článek alespoň v hrubých rysech splnil.





(ostatní články)


Copyright (c) Ondra Čada