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 slovocopy
(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
, čiautorelease
- 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 metodarelease
, 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. S
yntaxe 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 propertydataSource
adelegate
proUITableView
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í metodyretain
. 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 metodyretain
zavolá na proměnnou metoducopy
, čí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 NSAutoreleasePool
u. 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ž:
- 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“. - 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 jednoduchouif
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.
Šikovný urbi-et-orbi, jen tak dál a pověz to celému světu!