iphone programozás alapjai II. Gyakorlat
A mai gyakorlat témái I. Modell szétválasztás Modell logika osztályainak létrehozásának módjai Szakácsköny model kialakítása II. Hálózat kezelés Hálózat kezelés típusai ASI HTTP
I. MVC Architektúra
A hierarchia alapjai A nézetek egymásra rétegződnek A nézetek szülő - gyermek kapcsolatban állnak egymással Nézetet és controllereket létrehozhatunk IB-ben, vagy programatikusan, a controllerek implementációját el kell készíteni
Az aktív controller Mindig csak egy controller aktív Többféle módon tudunk váltani az aktív kontrollerek, vagy nézetek között addsubview - hozzáadja és megjeleníti a nézetet az aktuális nézethez, de a szülő nézet controllere marad aktív. [UIView presentmodalviewcontroller...] - modálisan megjeleníti a kiválasztott nézetet, aktivizálja a controllerét, és beállítja parentcontrollernek az előzőleg aktuális kontrollert. pushviewcontroller - NavigationController esetén ezzel a metódussal tudunk a stackhez hozzáadni egy új nézetet, ez lesz az aktív controller, és a szülője a navigation controller példánya lesz. Ilyenkor elérhető a navigationcontroller, vagy tabbarcontroller property az adott controllerben.
A projekt struktúrálása Nincsenek package-k Ún. groupokat hozhatunk létre (forrás könyvtárak) Érdemes elkülöníteni a model és controller osztályokat (esetleg view-t is - programozott nézetek) XIB-ek általában a Resources könyvtárban vannak Húzzuk át a controller osztályokat a Controller könyvtár alá
A singleton minta Objective-C-ben is gyakran használt pattern Igen jó minta a program funkcionális részeinek szeparálásához Modell funkciókra hasznos, mert így a legtöbb controllerből könnyen lehet kezelni az adatokat Példák: Működés főbb moduljainak megvalósítása Hálózati kapcsolatért felelős osztályok Utility osztályok
Singleton példa @interface MySingleton : NSObject { + (MySingleton*) getinstance; @end static MySingleton *instance = nil; +(MySingleton*)getInstance{! if (instance==null){!! @synchronized([uiapplication sharedapplication]){!!! if (instance==null) instance=[[mysingleton alloc] init];!!!! return instance;
Selector A @selector nem más mint egy módszer egy metódus kiválasztására.! SEL aselector = @selector(run);! [anobject performselector:aselector]; A selectoroknak fontos szerepe van ha több szálon dolgozunk A GUI-t nem szabad külső szálról módosítani // GUI módosítás beütemezése a fő szálba [handler performselectoronmainthread:@selector(messagearrived:) withobject:msg waituntildone:yes]; // Új szál indítása ha a szál implementációja az aktuális osztály [NSThread detachnewthreadselector:@selector(run) totarget:self withobject:nil];
Folytassuk a szakácskönyvet! Hozzunk létre egy CookBookManager singleton osztályt Készítsük el az adatokat reprezentáló modell osztályokat: Recipe Készítsünk metódusokat a CookBookManager osztályba a következő feladatokra: Ajánlatok Kategóriák lekérdezése Kategóriához tartozó receptek lekérdezése Kedvencek lekérdezése Hozzáadás a kedvencekhez
Recipe Osztály Készítsünk egy új osztályt (Recipe), amely egy recept tárolására szolgál (Recipe.h) @interface Recipe : NSObject {! NSString* title;! NSString* subtitle;! NSString* description;! NSString* complexity; - (id) initwithtitle:(nsstring*)_title andsubtitle:(nsstring*) _subtitle anddescription:(nsstring*)_description andcomplexity: (NSString*)_complexity; @property (nonatomic, retain) NSString* title; @property (nonatomic, retain) NSString* subtitle; @property (nonatomic, retain) NSString* description; @property (nonatomic, retain) NSString* complexity; @end
Recipe Osztály Recipe.m (végéről ne felejtsük el a dealloc-ot!) @implementation Recipe @synthesize title, subtitle, description, complexity; - (id) initwithtitle:(nsstring*)_title andsubtitle:(nsstring*) _subtitle anddescription:(nsstring*)_description andcomplexity: (NSString*)_complexity; {! self = [super init];! if(self!= nil) {!! self.title = _title;!! self.subtitle = _subtitle;!! self.description = _description;!! self.complexity = _complexity;!! return self;! @end
Adattárolás a memóriában Adatok (objektumok) tárolására használjuk valamelyiket az alábbiak közül: NSArray - tömb, mérete és tartalma a létrehozáskor eldől NSMutableArray - változó tartalmú és méretű tömb NSDictionary - kulcs - érték párokat tartalmazó tároló NSMutableDictionary - előzőhöz hasonló, változó méretű és tartalmú tároló
CookBookManager Osztály Készítsünk egy új osztályt (CookBookManager), amely kezeli a receptekkel kapcsolatos műveleteket, és tárolja az adatokat (CookBookManager.h) @interface CookBookManager : NSObject {!! NSMutableArray* hot;! NSMutableArray* favorites;! NSMutableArray* categories;! NSMutableDictionary* recipesbycategories; + (CookBookManager*) getinstance; - (NSArray*) gethot; - (NSArray*) getcategories; - (NSArray*) getrecipesbycategory: (NSString*) category; - (NSArray*) getfavourites; @end
CookBookManager Osztály CookBookManager.m Singleton minta kezelése static CookBookManager *instance = nil; + (CookBookManager*) getinstance { if (instance==null){ @synchronized([uiapplication sharedapplication]){ if (instance==null) instance=[[cookbookmanager alloc] init]; return instance;
CookBookManager CookBookManager.m Konstruktor - (id) init {! self = [super init];! if(self)! {!! hot = [[NSMutableArray alloc] initwithcapacity:5];!! favorites = [[NSMutableArray alloc] initwithcapacity:5];!! categories = [[NSMutableArray alloc] initwithcapacity:5];!! recipesbycategories = [[NSMutableDictionary alloc] init];!!!! [categories addobject:@"levesek"];!! [categories addobject:@"előtelek"];!! [categories addobject:@"húsételek"];!! Recipe* r = [[Recipe alloc] initwithtitle:@"bableves" andsubtitle:@"mari néni receptje alapján" anddescription:@"leírás" andcomplexity:@"30 perc"]; Recipe* r2 = [[Recipe alloc] initwithtitle:@"pulyka" andsubtitle:@"rózsi néni receptje alapján" anddescription:@"leírás" andcomplexity:@"45 perc"];...
CookBookManager CookBookManager.m Konstruktor!! NSMutableArray* soups = [[NSMutableArray alloc] initwithcapacity:5];!! [soups addobject:r];!! NSMutableArray* meats = [[NSMutableArray alloc] initwithcapacity:5];!! [meats addobject:r2];!!!! [recipesbycategories setvalue:soups forkey:@"levesek"];!! [recipesbycategories setvalue:meats forkey:@"húsételek"];!! [favorites addobject:r2];!! [hot addobject:r];!! return self;...
CookBookManager CookBookManager.m Lekérdező metódusok - (NSArray*) gethot {! return hot; - (NSArray*) getcategories {! return categories; - (NSArray*) getrecipesbycategory: (NSString*) category {! return [recipesbycategories objectforkey:category]; - (NSArray*) getfavourites {! return favorites;
Hozzáférés az adatokhoz Minden controllerben használjuk a singleton CookBookManagert Táblázat feltöltéséhez a UITableView néhány metódusát kell implementálni: numberofrowsinsection - megadja hány sort kell a táblázatnak megjeleníteni cellforrowatindexpath - az adott cella indexre visszaad egy cella nézetet, ami lehet bármi, akár egy tetszőleges saját nézet implementáció Töltsük fel minden listában a cellák adatait a megfelelő tartalommal Használjuk egyelőre a manager osztályban definiált mock tartalmat
CategoryViewController Kiegészítjük a múlt órán megírt kontrollert a modell rész használatával: - (NSInteger)tableView:(UITableView *)tableview numberofrowsinsection:(nsinteger)section {! return [[[CookBookManager getinstance] getcategories] count];
CategoryViewController Kiegészítjük a múlt órán megírt kontrollert a modell rész használatával: - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableview dequeuereusablecellwithidentifier:cellidentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initwithstyle:uitableviewcellstyledefault reuseidentifier:cellidentifier] autorelease];!! cell.imageview.image=[uiimage imagenamed:@"category.gif"];! cell.accessorytype=uitableviewcellaccessorydetaildisclosurebutton; cell.textlabel.text = [[[CookBookManager getinstance] getcategories] objectatindex:indexpath.row]; return cell;
Receptlista A fentiek elkészülte után most már megjelennek a kategóriák, úgy ahogy a modell részben meghatározásra kerültek. Csináljuk meg ugyanazt a recept listával is! Ez már egy kicsit összetettebb hiszen a receptlista van mind az ajánlatoknál, mind a kategóriákon belül. Ezért itt felveszünk egy @propertyt, ami azt tárolja van e kiválasztott kategória. Ha nincs akkor az ajánlatokat jelenítjük meg. Azért nem a konstruktort írjuk át, mert a tabokat nem mi példányosítjuk kódból.
RecipeListViewController Felvesszük a.h fájlba a propertyt és a segéd változót: @interface RecipeListViewController : UITableViewController {! NSString* selectedcategory; NSArray* recipes; @property (nonatomic, retain) NSString* selectedcategory; @property (nonatomic, retain) NSArray* recipes;; @end
RecipeListViewController Az.m fájlban megjelenéskor lekérjük az új listát, majd felhasználjuk: - (void)viewwillappear:(bool)animated { if (self.selectedcategory!=nil){ self.recipes=[[cookbookmanager getinstance] getrecipesbycategory:self.selectedcategory]; else { self.recipes=[[cookbookmanager getinstance] gethot]; - (NSInteger)tableView:(UITableView *)tableview numberofrowsinsection:(nsinteger)section {! if(self.recipes!=nil) {!! return [recipes count];!! else return 0;
RecipeListViewController // Customize the appearance of table view cells. - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath: (NSIndexPath *)indexpath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableview dequeuereusablecellwithidentifier:cellidentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initwithstyle:uitableviewcellstylesubtitle reuseidentifier:cellidentifier] autorelease];!! cell.imageview.image=[uiimage imagenamed:@"recipe.jpeg"];!! cell.accessorytype=uitableviewcellaccessorydetaildisclosurebutton;!! if(self.recipes!= nil) {!! Recipe* r = [recipes objectatindex:indexpath.row];!! cell.textlabel.text = r.title;!! cell.detailtextlabel.text= r.subtitle;!! return cell;
RecipeViewController Felvesszük a.h fájlban a recept elemeit (ne felejstük el IB-ben bekötni): @interface RecipeViewController : UIViewController {! UILabel* recipetitle;! UILabel* recipesubtitle;! UILabel* complexity;! UITextView* description;! Recipe* recipe;! @property (nonatomic,retain) IBOutlet UILabel* recipetitle; @property (nonatomic,retain) IBOutlet UILabel* recipesubtitle; @property (nonatomic,retain) IBOutlet UILabel* complexity; @property (nonatomic,retain) IBOutlet UITextView* description; @property (nonatomic,retain) Recipe* recipe; @end
RecipeViewController Az.m fájlban megjelenéskor betöltjük a mezőket - (void)viewwillappear:(bool)animated {!! if(self.recipe!= nil){!! self.title = self.recipe.title;!! self.recipetitle.text = self.recipe.title;!! self.recipesubtitle.text = self.recipe.subtitle;!! self.complexity.text = self.recipe.complexity;!! self.description.text = self.recipe.description;!!! [super viewwillappear:animated];
Kattintások kezelése Mind a kategóriák, mind a receptlista, mind a receptek már képesek megjelenni, de még nem készítettük el, hogy egy sorra való kattintáskor működjön is. Lényegében a didselectrowatindexpath hívást kell bővítenünk. Eddigi ismereteink alapján ezt próbáljuk meg önállóan megcsinálni!
CategoryViewController Megfelelően felparaméterezzük a receptlistát: - (void)tableview:(uitableview *)tableview didselectrowatindexpath: (NSIndexPath *)indexpath {!!! RecipeListViewController *recipelistviewcontroller = [[RecipeListViewController alloc] initwithnibname:@"recipelist" bundle:nil];!! NSString* category = [[[CookBookManager getinstance] getcategories] objectatindex:indexpath.row];!! recipelistviewcontroller.title = category;! recipelistviewcontroller.selectedcategory = category;!! [self.navigationcontroller pushviewcontroller:recipelistviewcontroller animated:yes];! [recipelistviewcontroller release];
RecipeListViewController Megfelelően felparaméterezzük a recept megjelenítőt: - (void)tableview:(uitableview *)tableview didselectrowatindexpath: (NSIndexPath *)indexpath {! RecipeViewController *recipeviewcontroller = [[RecipeViewController alloc] initwithnibname:@"recipe" bundle:nil];! recipeviewcontroller.recipe = [recipes objectatindex:indexpath.row];! [self.navigationcontroller pushviewcontroller:recipeviewcontroller animated:yes];! [recipeviewcontroller release];
Hol tartunk most?
II. Hálózatkezelés
Iphone a hálózaton Az Iphone része az IP hálózatnak amennyiben A felhasználónak van internethasználat engedélyezve a mobilszolgáltatónál (ez csak mobilhálózatra vonatkozik) Van megfelelő mobilhálózat Van elérhető Wifi a környezetben Mobile Network, 3G, Edge, etc... Wireless LAN
Áttekintés BSD Sockets, OpenSSL WebKit, CFNetwork Bonjour - itunes, ichat, printers, music sharing Peer2Peer - GameKit, Bluetooth, Bonjour ASIHTTPRequest - Third party library, POST
UIWebView Internetes oldalak egyszerű beépítése tetszőleges helyre WebView elérhető az InterfaceBuilderben Támogatott formátumok a HTML-en kívül: Excel, Keynote, Numbers, Pages, PDF, Powerpoint, Word, MHTML MS Office documentumok Word 97 formátumban működnek csak IPhone OS 3.0: RTF, Keynote, Number, Pages 09 verziók
NsUrlConnection Különböző protokollokat támogat Szinkron és aszinkron módon is tud működni, alapértelmezett üzemmód aszinkron A kérés kiszolgálásának folyamatát lehet követni, file fel/ letöltés hol tart, becslés számítása, stb. Feladatai: autentikáció, protokoll implementáció, cacheelés, cookie-k
ASIHTTPRequest NsUrlConnection körülményes Open source könyvtár Aszinkron és szinkron módon is tud működni HTTP post és file feltöltést egyszerűvé teszi http://allseeing-i.com/asihttprequest/ NSURL *url = [NSURL URLWithString:@"http://www.ponte.hu"]; ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url]; [request startsynchronous]; NSError *error = [request error]; if (error) {! NSLog(@"Error connecting... %@", error); else {! NSLog(@"Response arrived: %@", [request responsestring]);
JSON Javascript Object Notation Általános eszköz a webes technológiákban Tömörebb, hatékonyabb, mint az XML Jól használható Iphone alkalmazásokban, ha egy webes alkalmazáshoz kell kapcsolódni, könnyű az integráció { "firstname": "John", "lastname": "Smith", "age": 25, "address":{ "streetaddress": "21 2nd Street", "city": "New York", "state": "NY", "postalcode": "10021", "phonenumber":[{"type": "home","number": "212 555-1234", {"type": "fax", "number": "646 555-4567"]
JSON értelmezése JSON Api kiegészíti az NSString interface-t JSON feldolgozása innentől az NSString osztály JSONValue metódusa lesz Visszaadhat NSArray, NSDictionary a választól függően NSArray* recipesjson = [[request responsestring] JSONValue]; NSMutableArray* recipeslist = [NSMutableArray arraywithcapacity: [recipesjson count]]; for (NSDictionary* dict in recipesjson) {! Recipe* r = [Recipe recipewithdictionary:dict];! [recipeslist addobject:r];
Külső komponensek http://ponte.hu/oktatas/utils.zip Húzzuk be a Classes alá a utils könyvtárat Mindenképpen másoljuk be az állományokat a projekt alá Adjuk hozzá a projekthez a következő keretrendszereket: CFNetwork CoreGraphics MobileCoreFramework SystemConfiguration libz.1.2.3.dylib
Folytassuk a szakácskönyvet! Egészítsük ki a CookBookManager osztályunkat, hogy az adatokat mostantól a webes szerverünkről töltse le Ehhez hozzunk létre modell osztályokat, illetve egészítsük ki a már meglévőket, hogy a megfelelő adatokat tudjuk tárolni Használjuk az ASIHTTP API-t a szerverhívásokhoz A beérkező JSON választ dolgozzuk fel a JSON API segítségével Hozzunk létre objektumokat a JSON adatok alapján
Az interface Három nézetünk van jelenleg, amihez szerver kommunikáció szükséges, ezekhez pedig a következő adatokat kell lekérdeznünk: Aktuális ajánlatok Kategóriák Egy adot kategóriához tartozó receptek Tetszőleges recept az azonosítója alapján
A szerver Az alkalmazás szerver a következő lekérdezéseket támogatja: http://cookbookserver.appspot.com/gethots http://cookbookserver.appspot.com/getrecipe?recid=%@ http://cookbookserver.appspot.com/getcategories http://cookbookserver.appspot.com/getrecipesbycategory?catid=%@ Próbáljuk ki, ha beírjuk a böngészőbe mit kapunk, nézzük meg a forrást is.
Kibővítjük a Recipe osztályt Megjelent az imageurl mező Készítsünk egy factory metódust! ami a JSON adatokból létrehoz egy objektum példányt.. NSString* imageurl; + (id) recipewithdictionary: (NSDictionary*) dict; - (id) initwithtitle:(nsstring*)_title andsubtitle:(nsstring*) _subtitle anddescription:(nsstring*)_description andcomplexity: (NSString*)_complexity andimageurl:(nsstring*)_imageurl; @property (nonatomic, retain) NSString* imageurl;..
Kibővítjük a Recipe osztályt Vezessük át az új mezőt (property, konstruktor, synthesize, dealloc), valamint vegyük fel a factory metódust: + (id) recipewithdictionary: (NSDictionary*) dict {! Recipe* r = [[[Recipe alloc] init] autorelease];!! r.title = [dict objectforkey:@"title"];! r.subtitle = [dict objectforkey:@"subtitle"];! r.description = [dict objectforkey:@"description"];! r.complexity = [dict objectforkey:@"complexity"];! r.imageurl = [dict objectforkey:@"imageurl"];!! return r;
Category Létrehozunk egy Category osztályt is: @interface Category : NSObject {! NSString* catid;! NSString* name;! NSString* imageurl;! @property (nonatomic, retain) NSString* catid; @property (nonatomic, retain) NSString* name; @property (nonatomic, retain) NSString* imageurl; + (id) categorywithdictionary: (NSDictionary*) dict; @end
Category @implementation Category @synthesize name, catid, imageurl; + (id) categorywithdictionary: (NSDictionary*) dict {! Category* cat = [[[Category alloc] init] autorelease];!! cat.name = [dict objectforkey:@"name"];! cat.catid = [dict objectforkey:@"catid"];! cat.imageurl = [dict objectforkey:@"imageurl"];! return cat; -(void)dealloc{ [name release]; [catid release]; [imageurl release]; [super dealloc]; @end
CookBookManager Kibővítjük a metódusokat hálózati kommunikcióval - (NSArray*) gethotrecipes {! NSURL *url = [NSURL URLWithString:@"http:// cookbookserver.appspot.com/gethots"];! ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url];! [request setdefaultresponseencoding:nsutf8stringencoding];! [request startsynchronous];! NSError *error = [request error];! if (error) {!! NSLog(@"Error getting hot recipes from server: %@", error);!! return nil;! else {!! NSArray* recipesjson = [[request responsestring] JSONValue];!! NSMutableArray* recipeslist = [NSMutableArray arraywithcapacity:[recipesjson count]];!! for (NSDictionary* dict in recipesjson) {!!! Recipe* r = [Recipe recipewithdictionary:dict];!!! [recipeslist addobject:r];!!!! return recipeslist;!
CookBookManager - (NSArray*) getcategories {! NSURL *url = [NSURL URLWithString:@"http:// cookbookserver.appspot.com/getcategories"];! ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url];! [request setdefaultresponseencoding:nsutf8stringencoding];! [request startsynchronous];! NSError *error = [request error];! if (error) {!! NSLog(@"Error getting categories from server: %@", error);!! return nil;! else {!! NSArray* categoriesjson = [[request responsestring] JSONValue];!! NSMutableArray* categorylist = [NSMutableArray arraywithcapacity:[categoriesjson count]];!! for (NSDictionary* dict in categoriesjson) {!!! Category* c = [Category categorywithdictionary:dict];!!! [categorylist addobject:c];!!!! return categorylist;!
CookBookManager - (NSArray*) getrecipesbycategory: (NSString*) catid {! NSURL *url = [NSURL URLWithString:[NSString stringwithformat:@"http://cookbookserver.appspot.com/ getrecipesbycategory?catid=%@", catid]];! ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url];! [request setdefaultresponseencoding:nsutf8stringencoding];! [request startsynchronous];! NSError *error = [request error];! if (error) {!! NSLog(@"Error getting recipes for category from server: %@", error);!! return nil;! else {!! NSArray* recipesjson = [[request responsestring] JSONValue];!! NSMutableArray* recipeslist = [NSMutableArray arraywithcapacity:[recipesjson count]];!! for (NSDictionary* dict in recipesjson) {!!! Recipe* r = [Recipe recipewithdictionary:dict];!!! [recipeslist addobject:r];!!!! return recipeslist;!
CookBookManager - (Recipe*) getrecipebyid: (NSString*) recid {! NSURL *url = [NSURL URLWithString:[NSString stringwithformat:@"http://cookbookserver.appspot.com/getrecipe? recid=%@", recid]];! ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url];! [request setdefaultresponseencoding:nsutf8stringencoding];! [request startsynchronous];! NSError *error = [request error];! if (error) {!! NSLog(@"Error getting recipe from server: %@", error);!! return nil;! else {!! NSDictionary* dict = [[request responsestring] JSONValue];!! return [Recipe recipewithdictionary:dict];!
Controllerek Módosítsuk a kontrollereinket, hogy a plussz adatokat is megjelenítsék (CategoryViewController)! - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath {... Category* category=[[[cookbookmanager getinstance] getcategories] objectatindex:indexpath.row];! NSURL *url = [NSURL URLWithString:cat.imageURL];! NSData *data = [NSData datawithcontentsofurl:url];! UIImage *img = [UIImage imagewithdata:data];! cell.imageview.image = img; cell.textlabel.text = cat.name; return cell;
Controllerek Módosítsuk a kontrollereinket, hogy a plussz adatokat is megjelenítsék (RecipelistViewController)! - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath {...!! Recipe* r = [recipes objectatindex:indexpath.row];!! cell.textlabel.text = r.title;!! cell.detailtextlabel.text= r.subtitle;!! NSURL *url = [NSURL URLWithString:r.imageURL];!! NSData *data = [NSData datawithcontentsofurl:url];!! UIImage *img = [UIImage imagewithdata:data];!! cell.imageview.image = img;!! return cell;
A recept Hozzunk létre egy UIImageView típusú adattagot a recept nézet controllerébe Kössük be az Interface Builderben a képhez tartozó elemet is - (void)viewwillappear:(bool)animated {! if(self.recipe!= nil){!! self.recipetitle.text = self.recipe.title;!! self.recipesubtitle.text = self.recipe.subtitle;!! self.timetomake.text = @"30 perc";!! self.description.text = self.recipe.description;!! NSURL *url = [NSURL URLWithString:recipe.imageURL];!! NSData *data = [NSData datawithcontentsofurl:url];!! UIImage *img = [UIImage imagewithdata:data];!! self.imageview.image = img;!!! [super viewwillappear:animated];
A memória felszabadítása Ne feledjük el felszabadítani a lefoglalt memóriát A nézetekben tárolt listákat végül magunknak kell felszabadítani Receptlista nézetben a recepteket Recept nézetben a receptet - (void)dealloc {! [recipes release]; [super dealloc];
Próbáljuk ki!
Köszönöm a figyelmet! Sallai Péter peter.sallai@ponte.hu