Zpět Obsah Další

Objective C: příklad 2
Tvorba vlastní třídy


Tentokrát si ukážeme prostředky, které Objective C nabízí pro tvorbu vlastních tříd. Vytvoříme třídu, která umožní práci s čísly modulo N (přičteme-li k hodnotě N-1 jedničku, dostaneme v této algebře nulu) a na triviálním příkladu ukážeme jak nová třída pracuje.

Program uložíme do tří souborů: soubor 'sample2.h' bude obsahovat rozhraní třídy (interface); kdokoli kdo bude chtít naši třídu používat pak bude moci rozhraní importovat. Druhý soubor, 'sample2.m', obsahuje implementaci třídy — v něm je popsáno chování objektů při přijetí zprávy. Poslední soubor — 'sample2m.m' — obsahuje program, který služeb nové třídy využívá (podobně jako minulý příklad využíval služeb třídy NSMutableDictionary).

// Objective C — příklad 2/1, soubor sample2.h
//
// ukázka interface nové třídy

#import <Foundation/Foundation.h>

@interface ModN: NSObject // třída se jmenuje ModN a je dědicem základní třídy NSObject
{ // její objekty obsahují proměnné:
  int N; // N — modul
  int value; // hodnota čísla
}
// následující metody jsou metodami třídy ModN, nikoli jejích objektů
// překladači to sdělíme úvodním znaménkem '+':
+(int)minN; // minimální modul (vrací číslo)
+(ModN*)modNWithN:(int)N; // tvorba nového objektu se zadaným modulem
// následující metody jsou interpretovány objekty třídy ModN
// překladači to sdělíme úvodním znaménkem '-':
-initWithN:(int)N; // inicializace nového objektu, nastavení modulu
-set:(int)i; // nastavení na celočíselnou hodnotu i
-(int)get; // zjištění hodnoty
-add:n; // přičtení čísla n (není-li uveden typ, je parametr typu id)
-sub:n; // odečtení čísla n
-zero; // vynulování čísla n
@end
// end of file

Podívejme se podrobněji na jednotlivé části programu:

1. Import souboru Foundation.h — sample2.h

Hlavičkový soubor 'Foundation.h' obsahuje interface standardní třídy NSObject, která je základem celého systému tříd prostředí Cocoa. Importujeme jej proto, že budeme vytvářet novou třídu jako dědice třídy NSObject. Pokud bychom pracovali v samostatném Objective C mimo prostředí Cocoa, mohli bychom stejně dobře importovat hlavičkový soubor 'Object.h' a použít třídu Object.

2. Direktiva @interface — sample2.h

Direktiva @interface v Objective C uvádí rozhraní třídy. Za direktivou se uvádí jméno nově vytvářené třídy, dvojtečka a jméno třídy, kteoru chceme použít jako vzor. Zde tedy vytváříme třídu jménem ModN jako dědice třídy NSObject.

Rozhraní třídy je ukončeno direktivou @end, kterou vidíme na samém konci souboru 'sample2.h'.

3. Deklarace proměnných — sample2.h

Bezprostředně za direktivou @interface následuje deklarace proměnných objektu ('properties') — jedná se o blok proměnných, který bude každý objekt třídy ModN reprezentovat v paměti (společně s případnými dalšími proměnnými, zděděnými po třídě NSObject).

Deklarace proměnných je přesnou analogií deklarace struktury: mezi dvojicí složených závorek deklarujeme libovolné proměnné. Objekty třídy ModN tedy budou obsahovat dvojici celočíselných proměnných: hodnotu N, určující modul, a momentální hodnotu objektu uloženou v proměnné value.

4. Deklarace metod — sample2.h

Za deklarací proměnných objektu následuje deklarace jeho metod. Jméno metody je stejné jako jméno zprávy, která tuto metodu vyvolá; můžeme se tedy na deklaraci metod dívat také jako na seznam zpráv, které bude objekt nové třídy schopen zpracovat.

Deklarace metod je podobná deklaraci funkcí; parametry však uvádíme za dvojtečkami a typy zapisujeme do závorek. Neuvedeme-li žádný typ, předpokládá Objective C typ id.

V Objective C je třída sama také objektem; může proto sama zpracovávat zprávy. Deklaraci metod třídy uvedeme znakem '+'; deklaraci metod objektu znakem '-'. Metody třídy mají dva hlavní účely:

Povšimněme si toho, že metoda třídy modNWithN: nevrací id, ale ModN*. Z hlediska běhu programu je to naprosto jedno, a kdybychom typ id použili, vše by fungovalo naprosto stejně dobře (nebo stejně špatně); dáváme tím však překladači více informací pro kontrolu při překladu, aby např. na příkazu JinaTrida* x=[ModN modNWithN:3] ohlásil varování.

// Objective C — příklad 2/2
//
// ukázka implementace nové třídy

#import "sample2.h" // importujeme interface

@implementation ModN // rodiče a proměnné není třeba uvádět — jsou známy z interface
+(int)minN // metody se definují stejně jako běžné funkce
{
  return 2;
}
-(int)get
{
  return value;
}
-set:(int)i
{
  value=i%N;
  return self;
}
-zero
{
  return [self set:0];
}
-add:n
{
  return [self set:value+[n get]];
}
-sub:n
{
  return [self set:value-[n get]];
}
-init
{
  return [self init:10];
}
-initWithN:(int)n
{
  [super init];
  if (n<[[self class] minN]) N=[[self class] minN];
  else N=n;
  return [self zero]; // vynulujeme
}
+
(ModN*)modNWithN:(int)n;
{
  return [[[self alloc] initWithN:n] autorelease];
}
@end
// end of file

Podívejme se podrobněji na jednotlivé části programu:

5. Direktiva @implementation — sample2.m

Direktiva @implementation v Objective C uvádí implementaci třídy. Za direktivou se uvádí jméno nově vytvářené třídy; ostatní údaje překladač převezme automaticky z rozhraní. Implementace je ukončena stejně jako rozhraní direktivou @end. Povšimněme si také přípony '.m': Objective C používá standardně tuto příponu pro moduly, obsahující implementace tříd.

6. Implementace metod — sample2.m

Implementace metod je přesnou obdobou implementace funkcí v běžném jayzce C: zopakujeme hlavičku metody, a namísto středníku za ni zapíšeme blok, obsahující tělo metody.

Implementace metody minN je zcela triviální. Metoda get ukazuje pouze to, že metody mají přímý přístup k proměnným objektu — použijeme-li tedy uvnitř metody proměnnou value, budeme pracovat s obsahem proměnné value uvnitř toho objektu, který zprávu dostal.

Metoda set: navíc ilustruje často využívaný trik: nemá-li některá metoda co rozumného vrátít, vrátí identifikaci objektu, který zprávu zpracovává; tuto identifikaci máme kdykoli k dispozici ve speciální proměnné self. Díky tomuto triku můžeme zprávy pohodlně řetězit — lze například používat výrazy typu [[[obj initWithN:3] set:2] add:n].

V implementaci metody zero vidíme, že proměnnou self lze použít také chce-li objekt poslat zprávu sám sobě: objekt vynuluje svůj obsah prostřednictvím metody set:. Tento přístup je mnohem flexibilnější, než kdybychom použili přímo výraz value=0; připomeneme si jej v příštím pokračování tohoto seriálu. Metody add a sub jsou implementovány podobně; obě navíc pouze zjistí pomocí zprávy get jaká je hodnota objektu, který má být přičten nebo odečten. Konečně metoda init je analogicky implementována nepřímo pomocí metody initWithN:; i k tomu se vrátíme v příštím příkladu.

Metoda initWithN: nejprve musí zajistit inicializaci zděděných vlastností objektu (v C++ nebo Javě je to zajištěno automaticky; jak uvidíme, je to zbytečné — jen to kompikuje jazyk, a výrazně snižuje flexibilitu). Použijeme tedy speciálního příjemce super;  ten zajistí — podobně jako proměnná self — že objekt odešle zprávu sám sobě; navíc však se při použití příjemce super použije implementace metody init uvnitř nadřízené třídy. To je důležité — pokud by se použila standardním způsobem implementace uvnitř této třídy, byla by vyvolána "naše" metoda init, která sama opět volá metodu initWithN: — došlo by tedy ke vzájemnému rekurzivnímu volání, které by bylo ukončeno až přetečením zásobníku.

Nakonec si uvědomíme, jak je implementována 'třídní' metoda modNWithN:. Jedná se o metodu třídy, proměnná self tedy reprezentuje třídu. Standardní zpráva alloc, odeslaná třídě, vytvoří nový objekt a vrátí jeho identifikaci; tomuto novému objektu tedy je ihned odeslána zpráva initWithN:. Tím jsme skoro hotovi; poslední zpráva autorelease jen předá nově vytvořený objekt do správy garbage collectoru, aby mohl být podle potřeby automaticky uvolněn.

// Objective C — příklad 2/3
//
// ukázka použití nové třídy

#import "sample2.h" // importujeme interface

void main()
{
  NSAutoreleasePool *pool=[
NSAutoreleasePool new]; // garbage collector
  ModN *n1;
  id n2;

  n1=[ModN new:4];
  n2=[ModN new:20];
  if (0) { // don't do it!
    [n1 alloc]; // překladač ohlásí varování
    [n2 alloc]; // překladač nevaruje
  }
  // inicializujeme obě čísla stejně, vzhledem k různým modulům však bude
  // nastavená hodnota různá:
  #define INIT 30
  printf("Init %d, n1=%d, n2=%d\n",INIT,[[n1 set:INIT] get],[[n2 set:INIT] get]);
  // vypíše se "Init 30, n1=2, n2=10"
  printf("n1+n2=%d\n",[[n1 add:n2] get]);
  // vypíše se "n1+n2=0"
  [pool release];
}
// end of file

Podívejme se podrobněji na jednotlivé části programu:

7. Použití nové třídy — sample2m.m

Samo použití nové třídy je triviální — prostě vytvoříme dva objekty třídy ModN, určíme jejich hondoty, sečteme je a to je vše. V příkladu 'sample2m.m' si však v praxi ukážeme varování, jež může překladač podávat.

Známe-li předem třídu, můžeme namísto obecné deklarace id pro identifikaci objektu použít také typ <třída>* — proměnnou n1 tedy deklarujeme jako identifikaci objektu třídy ModN, zatímco proměnná n2 je deklarována jako identifikátor objektu libovolné třídy. K čemu je taková deklarace dobrá ilustruje příklad if (0)...: objekty třídy ModN nejsou schopny reagovat na zprávu alloc (a kdybychom jim ji opravdu poslali, program by skončil běhovou chybou). V prvním případě však překladač — na základě deklarace — ví, že n1 reprezentuje objekt třídy ModN, a proto při překladu vydá varování.


Zpět Obsah Další

Copyright © Chip, O. Čada 2000-2003