Cocoa Objective-C a práce s pamětí

Práce s pamětí v objective-c je velmi zásadní (přece jen je to klasické Cčko v novém kabátě). Zvlášť, pokud programujeme pro iPhone, který nepoužívá garbage collector, musíme se o vše postarat sami. Určitě jste se někdy setkali s názvy jako alloc, init, release, dealloc, retain, či autorelease. Z Cčka víme, že pokud nějakou proměnnou vytvoříme pomocí malloc, nesmíme ji zapomenout zase odalokovat pomocí free. Obdobně v C++ je to new a delete. V Objective-C je to podobné, abychom mohli správně uvolnit již nepotřebnou paměť, musíme vědět, kdo je vlastníkem daného objektu.

V tomto článku si povíme kdy a jak správně uvolňovat objekty, které používáme, představíme si objective-c property a vysvětlíme si různé typy, kterými property pracují s pamětí. Na závěr si povíme něco o automatickém uvolňování paměti (nikoliv o garbage collectoru).

Retain Count – počítáme reference

Práce s pamětí v objective-c a Cocoa frameworku je založena na počítání referencí (odkazů) na objekty. To znamená, že pokud si vytvoříme nějaký objekt – jsme jeho vlastníkem – zvýší se jeho počet referencí (retain count nebo také reference count) o číslo 1. Pokud tento objekt již nechceme používat, naznačíme to tím, že jeho retain count opět snížíme. Jakmile počet referencí objektu dosáhne hodnoty 0, objekt bude automaticky odalokován. Ale pozor! Ne vždy jsme vlastníkem objektu, přestože ho používáme.

Vlastnictví objektů

  • Vlastníkem objektu jsme tehdy, pokud ho sami vytvoříme. Objekty se vytváří pomocí metod začínajících na alloc, new nebo metod obsahujících slovo copy (např. alloc, newObject, mutableCopy)
  • Vlastnictví objektu se dá získat také prostřednictvím metody retain, o které si povíme za chvíli
  • Pokud jsme vlastníky objektu, jsme zodpovědní za jeho uvolnění (relinquishing ownership) – typicky pomocí metod release, či autorelease
  • Naproti tomu, pokud objekt nevlastníme, nesmíme ho uvolňovat

Několik příkladů vytvoření objektů:

NSString *message = [[NSString alloc]
                      initWithString:@"Toto je muj retezec."];
NSString *copiedMessage = [message copy];
copiedMessage = @"Tento retezec je take muj!";

NSLog(message);
NSLog(copiedMessage);

[message release];
[copiedMessage release]

Doplňme tedy, že při vytvoření objektu se automaticky nastaví retain count na hodnotu 1. Metoda release nedělá nic jiného, než že tuto hodnotu opět sníží (vlastně dělá ještě něco jiného – vrací self, tzn. objekt, jehož reference count byl snížen).

Vypůjčené objekty

Ptáte se nyní, jakéže jsou ty objekty, které používáme ale nevlastníme je?
Jejich vytváření mají na starosti třídní metody (tzv. Convenience Methods). To jsou metody, které většinou na začátku svého jména specifikují datový typ, který vrací v návratové hodnotě. Tedy například u třídy NSString to může být stringWithString:, u třídy NSDate date u NSArray arrayWithObjects: a mnoho dalších. U těchto objektů nejsme vlastníky, třída je pro nás vytvoří a předá nám pouze referenci (odkaz na paměť), kde se tento objekt vyskytuje. Jelikož si ho třída vytvořila sama, tak se také sama postará o jeho správné uvolnění.

NSString *message = [NSString
                     stringWithString:@"Tento retezec nevlastnim ja."];
message = @"Ale ja chci svuj retezec...";

NSString *myOwnMessage = [message retain];
myOwnMessage = @"Jupi, ted mam svuj vlastni retezec";
message = @"A muzu si obsah sveho vlastniho retezce zmenit i v promenne message";

NSLog(message);
NSLog(myOwnMessage);

[myOwnMessage release];
// nikoliv [message release]

Pro úplnost se ještě podíváme na následující ukázku kódu :

NSString *myString = [[NSString alloc] initWithString: @"tohle je muj retezec"];
NSString *mySecongString = [[NSString
                             stringWithString:@"tenhle je taky muj"] retain];

//naproti tomu
NSString *aString = [NSString stringWithString:@"tenhle retezec ale neni muj"];
NSString *aSecondString = [[[NSString alloc]
                            initWithString: @"a tenhle rovnez neni muj"] autorelease];

[myString release];
[mySecondString release];

Z posledního ukázkového kódu by mělo být také zřejmé, jaký je význam použitých metod.

  • Metoda retain zvyšuje čítač referencí u daného objektu (pro lepší použitelnost opět vrací self).
  • Metoda autorelease dělá velmi podobnou věc, jako metoda release, akorát s tím rozdílem, že čítač referencí sníží až někdy později, automaticky.

Ddy se teda uvolní objekty, kterým jsme zaslali zprávu autorelease, to se dozvíme až, si budeme povídat o NSAutoreleasePool.

Objective-C 2.0 Property to vyřeší za nás

Možná znáte property z jiných programovacích jazyků, jako například z C#, či Javy. Možná netušíte, co to property jsou – jsou to prostě gettery a settery.

Nastavení property nikdy nebylo jednodušší

Objective-C 2.0 nám umožňuje definovat tyto metody, aniž bychom museli mít přímo jejich implementaci ve zdrojovém kódu. Můžeme si zvolit jakým způsobem tyto metody budou zacházet s pamětí privátních proměnných – jestli zvýší retain count nebo zkopírují celý objekt do nového, či jen přiřadí ukazatel na adresu. A jak na to?

Retain, copy, assign, nonatomic nebo readonly …?

Nejdůležitější nastavení je potřeba učinit při definici property v hlavičkovém souboru – klíčovým slovem @property. Syntaxe je následující:

@property [(attribute [, attribute2, ...])] type name;

Pak už jen stačí v souboru s implementací říct kompilátoru slovíčkem @synthesize, ať se o vše postará sám.

Právě význam volitelných atributů je potřeba si pořádně vysvětlit – určitě jste se už setkali s definicí jako @property (nonatomic, retain) ... ale je nutno zdůraznit že toto není zdaleka jediný a ne vždy ten nejlepší způsob, jak bychom mohli své property definovat.

Atributy a správa paměti

Z hlediska správy paměti máme celkem 3 možnosti, jak se můžou vygenerované settery chovat:

  • assign
    obyčejné přiřazení – tímto atributem říkáme, že do privátní proměnné se má přiřadit adresa objektu, kterým nastavujeme hodnotu (parametr setteru), ale retain count není zvýšen. Toto je defaultní typ, který se použije, když nespecifikujeme žádný atribut, a většinou  je toto chování pro nás nežádoucí, ale například property dataSource a delegate pro UITableView právě tento přístup k paměti používají.
  • retain
    říká, že setter přiřadí adresu objektu ke do privátní proměnné a zároveň zvýší její retain count pomocí metody retain. Díky tomu objekt může s proměnnou pracovat tak dlouho, dokud si ji sám neuvolní. Ale pozor, pracujeme pořád se stejnou adresou, takže pokud objekt, který jsme do proměnné přiřadili, někdy později změní svoji hodnotu, projeví se to i v instanční proměnné. Tento způsob používáme nejčastěji.
  • copy
    pokud potřebujeme zajistit, aby hodnotu privátní proměnné nebylo možné měnit z vnějšku, specifikujeme právě tento atribut. Který namísto metody retain zavolá na proměnnou metodu copy, čímž zkopíruje obsah přiřazovaného objektu na jiné paměťové místo a tím tak zajistí nezávislost na původním objektu.

Vygenerované settery mohou vypadat zhruba následovně:

// @property NSString *string;
-(void)setString:(NSString *)newString {
    if (string != newString) {
        string = newString;
    }
} 

// @property (retain) NSString *string;
-(void)setString:(NSString *)newString {
    if (string != newString) {
        [string release];
        string = [newString retain];
    }
} 

// @property (copy) NSString *string;
-(void)setString:(NSString *)newString {
    if (string != newString) {
        [string release];
        string = [newString copy];
    }
}

Pokud od getterů, či setterů vyžadujeme, musíme si je už definovat sami.

Property umožňují definovat více atributů, než jen výše uvedené. Například nonatomic, či readonly. Ale jelikož tyto se přimo netýkají správy paměti, nebudu se o nich zde zmiňovat.

Automatické uvolnění paměti

Položili jste si někdy otázku jaký je rozdíl mezi release a autorelease? Cože tam má jedna metoda automatického a druhá ne? Odpovědí je NSAutoreleasePool.

Hromádka odložených objektů

Autorelease pool je vlastně jakýsi manažer paměti, který se stará právě o ty objekty, kterým zašleme autorelease namísto release. Všechny takovéto objeky se umístí do tohoto uložitě a fyzicky se uvolní až tehdy, když je uvolněn samotný objekt NSAutoreleasePoolu. Všimli jste si, že v aplikaci používate autorelease ale přitom jste nikde instanci třídy NSAutoreleasePool nevytvářeli? Tak se podívejte do souboru main.m kteréhokoliv XCode projektu iPhonové aplikace. Autorelease poolů se ale vytváří bez našeho vědomí více. Application Kit automaticky vytváří pooly při začátku cyklu událostí (event cycle, či event-loop). Zde se zachytávají například události generované při kliknutí na prvek GUI a ostatní vnitřní záležitosti dějící se v aplikaci. Na konci tohoto event-loopu je pool zase automaticky uvolněn.

Můžu si vytvořit své vlastní samouvolňovací jezírko?

Samozřejmě, že ano. A v některých situacích je to dokonce velmi vhodné, až nezbytné. Při vývoji iPhonových aplikací je to typicky, když:

  1. používáme svá vlastní vlákna.
    Každé vlákno musí obsahovat autorelease pool. Nejčastěji si programátor vytvoří tento pool na začátku metody běžící ve vláknu a na konci metody pool zase uvolní. A pokud byste na to náhodou zapomněli nezoufejte, konzole vás na to upozorní hromadou krásných varování končících nějak podobně jako „object is just leaking“.
  2. pokud v kódu máme cyklus vytvářející hodně pomocných objektů.
    Pokud máme cyklus a v každé iteraci potřebujeme vytvořit hodně pomocných objektů, může se hodit ušetřit paměť pomocí vytvoření autorelease poolu na začátku iterace cyklu a na konci iterace ho zase uvolnit. Docílíme tak uvolnění paměti hned jak ji skutečně nepotřebujeme, nikoliv akumulování a čekání až skončí event-loop případně jiný pool. Úplně ideální je uvolnit pool ne při každém průchodu, ale třeba jen v každé sté iteraci. To lze vyřešít jednoduchou if podmínkou.

Při vytváření vlastních poolů platí pravidlo, že pool se uvolňuje ve stejné oblasti viditelnosti (scope) jako byl vytvořen, tedy vytvoříme-li ho na začátku metody, na konci ho uvolňíme, vytvoříme-li ho uvnitř cyklu, před koncem cyklu uvolníme atp.

Doporučuji k přečtení

Ačkoliv tento článek je docela obsáhlý, poskytuje jen tu nejnutnější množinu potřebných znalostí. Pokud chcete umět opravdu dobře zacházet s pamětí v Cocoa objective-c, doporučuji prostudovat si následující zdroje.

One thought on “Cocoa Objective-C a práce s pamětí

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Můžete používat následující HTML značky a atributy: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

CommentLuv badge