http://alkalmazasfejlesztes.blogspot.com/
BecomeAnXcoder Bert Altenburg, Alex Clarke és Philippe Mougin
Ez a könyv egy relatíve fájdalommentes bevezetést biztosít számodra a Macintosh programozásba, bemutatja az Xcode használatának és az Objective-C programozási nyelvnek az alapjait.
2010.
Tartalomjegyzék 0: Xcoder - Az előszó .............................................................................................................3 0: Xcoder - Bevezetés .............................................................................................................4 00: Előkészítés .......................................................................................................................4 01: A program utasítások egy sorozata....................................................................................6 02: Nincs megjegyzés? Ez elfogadhatatlan! ..........................................................................14 03: Függvények ....................................................................................................................16 04: Kiíratás a képernyőre ......................................................................................................22 05: Program fordítása és futtatása .........................................................................................27 06: Feltételes elágazások ......................................................................................................38 07: Ciklusok .........................................................................................................................41 08: Grafikus interfészt használó program ..............................................................................44 09: Keresési módszerek ........................................................................................................64 10: awakeFromNib ...............................................................................................................68 11: Pointerek ........................................................................................................................70 12: Sztringek ........................................................................................................................73 13: Tömbök ..........................................................................................................................79 14: Tulajdonságok és kellékek ..............................................................................................82 15: Memóriakezelés..............................................................................................................87 16: Információforrások .........................................................................................................90
-2-
Bevezetés Az Apple ingyenesen biztosít minden szükséges segédeszközt annak érdekében, hogy nagyszerű Cocoa programokat írhass. A fejlesztő környezet neve Xcode, ami a Mac OS X tartozéka, de az Apple web oldalának fejlesztői szekciójából is letölthető. Több nagyon jó Mac-es programozással foglalkozó könyv is létezik, de mindegyik feltételez bizonyos programozási alapismereteket. Ez a könyv viszont egyáltalán nem, megtanít téged az Objective C programozás alapjaira Xcode környezetben. Az első 5 fejezet után már képes leszel alapvető programokat írni, amelyek még nem használják a grafikus környezetet. A következő néhány fejezetben pedig megtanulod, hogyan kell egyszerű programokat írni a grafikus környezetben (GUI). Miután befejezted ezt a kis könyvet már elegendő ismeret áll majd rendelkezésedre ahhoz, hogy a fent említett könyvek tartalmát is megismerd. Azokat is tanulmányozni kell majd, mert sok a tanulnivaló. De most nem kell félni, mert a következő oldalak igazán egyszerűek lesznek számodra.
-3-
Hogyan használjuk ezt a könyvet? Bizonyos részeket az alábbi módon kiemeltünk: Kiemelt rész Javasoljuk, hogy minden fejezetet legalább kétszer olvass el. Első alkalommal az így kiemelt részeket nyugodtan kihagyhatod. Második alkalommal azonban már ezeket is feltétlenül olvasd el. Ezáltal lehetőséged van folyamatosan nehezedő ismeretek elsajátítására. Több tucat egy, vagy több soros példaprogramot fogunk bemutatni. A könnyebb megfeleltetés érdekében ezekre a programokra szögletes zárójelbe tett számokkal, mint például: [1] fogunk hivatkozni, illetve ezen belül a második szám a programkód megjelölt sorára mutat majd. Például az [1.1], ahogy azt itt is láthatjuk: //[1] volume = baseArea * height;
// [1.1]
A programozás nem egyszerű munka. Kitartásra van szükség és arra, hogy a következő oldalakon bemutatott példákat próbáld ki magad is. Nem lehet megtanulni zongorázni, vagy autót vezetni úgy, hogy könyveket olvasgatunk róluk. Ugyanez vonatkozik a programozásra is. Ez egy elektronikus könyv amit a számítógépeden olvasol, ezért nincs kibúvó, minél gyakrabban válts az Xcode-ra és gyakorolj. Teszteld le az összes példát és próbáld kicsit módosítani azokat annak érdekében, hogy megismerd hogyan is működnek valójában.
Előkészítés
Ezt a könyvet Neked írtuk! Cserébe csak annyit kérünk, hogy támogasd a Macintosh platformot. Ez igazán nem lesz megterhelő számodra. A következőkre gondolunk. Minél hatékonyabban használod a Mac gépedet, annál könnyebben tudsz irányítani másokat is a Mac világába. Tehát mindig legyél képben: olvassál Mac-es magazinokat, rendszeresen látogassál Mac-kel kapcsolatos web oldalakat. Érdemes megtanulni az Objective C és az AppleScript használatát és használni is őket. Az AppleScript használata az üzleti munkádban sok pénzt és időt takaríthat meg számodra. Ajánljuk figyelmedbe Bert ingyenes könyvecskéjét: AppleScript abszolút kezdőknek Mutasd meg a világnak, hogy nem mindenki használ PC-t. Ha nyílvános helyen egy szép -4-
Mac-es pólót viselsz az is nagyon jó figyelem felhívó hatással lehet, de a következő módon is hasznossá teheted a gépedet. Ha elindítod az Activity Monitort (amit az Applications folderben a Utilities-ben találsz), akkor megfigyelheted, hogy nagyon ritkán használja ki teljes kapacitását. Tudósok számos olyan projektet (DC projects) dolgoztak ki, amelyek segítségével az egyes lokális számítógépek kihasználatlan számítási kapacitását összegyűjtve, azt közösségi célokra használják fel. Ilyen például a Folding@home, vagy a SETI@home. Neked csak egy kis ingyenes programot kell letöltened (DC kliens) és futtatni a gépeden. Ezek a DC kliensek a lehető legalacsonyabb prioritási szinten dolgoznak. Ez azt jelenti, hogy abban a pillanatban, amikor más programoknak a gépeden szüksége van erre a számítási kapacitásra is, akkor a DC kliens automatikusan a háttérbe vonul, leáll ha szükséges. Egyáltalán nem érzékeled, hogy ez a program használja a számítógépedet. Hogyan segíti ez a Macintosh-t? A legtöbb DC kliens rangsorolja a résztvevőket. Tehát ha egy Mac-es csoporthoz csatlakozol, akkor segítheted a Mac-es csoportot egy jobb helyezés eléréseben, ami a rangsorban jól látható. Tehát más platformot használó felhasználók láthatják, hogy a Macintosh-ok milyen jól teljesítenek. Különböző témákban találhatsz DC klienseket: matematika, gyógyítás ... stb. A következő oldalon megtalálhatod azt, amelyik a legjobban tetszik Neked: http://distributedcomputing.info/projects.html Csak egy probléma van ezzel a javaslattal: könnyen addiktívvá válhat! Figyelj arra, hogy a Macintosh platform a legjobb programokat használja. Itt nem csak arról van szó, hogy törekedj arra, hogy te nagyszerű programokat fejlessz. Írjál udvarias megjegyzéseket azoknak a programfejlesztőknek, akiknek a programjait használod. Még ha csak egy apró részletet is találtál a programban ami neked nem tetszett, írd meg a fejlesztőnek, hogy miért. Amilyen részletesen csak lehet írd meg azokat a körülményeket, amelyek között esetleg hibát találtál. Fizess az általad használt szoftverekért. Ameddig a Macintosh piac életképes, a fejlesztők biztosan továbbra is kiváló programokat fognak készíteni. Kérlek lépj kapcsolatba legalább 3 Mac-es ismerősöddel akiket érdekelhet ez a téma, hívd fel a figyelmüket erre a könyvre és arra, hogy hol található, vagy mondd el nekik a fenti tanácsokat. Ok, miközben letöltöd a DC klienst, már kezdhetjük is a tanulást!
-5-
01: A program utasítások egy sorozata BecomeAnXcoder - 01: A Program Is a Series of Instructions
A program utasítások egy sorozata
Bevezetés Amikor autót tanulsz vezetni, meg kell tanulnod egyszerre több dologra is figyelni: a gáz, a fék pedálra, a kuplungra. A programozásnál is hasonló a helyzet, több dologra is oda kell figyelni, különben a program lefagyhat. Ugyanakkor az autóvezetés elkezdéséhez szükséges, hogy megismerd az autó belsejét, de a programozáshoz nem feltétlenül kell rögtön az Xcode környezet megismerése. Az egyszerűség kedvéért ezt egy kicsit később mutatjuk majd be. Első lépésben néhány nagyon egyszerű matematikai példán keresztül bevezetünk az Objective C programozás alapjaiba. Már az elemi iskolában találkoztál olyan egyszerű számítási feladatokkal, ahol az eredményt egyszerűen be kell helyettesíteni a pontok helyére: 2 + 6 = ... ... = 3 * 4 (a csillag a szorzás elfogadott jelölése a számítástechnikában)
Felsőbb iskolai osztályokban a pontok kimentek a divatból, helyükre x, y változók kerültek, és már algebrai feladatként kezdtük becézni a korábbi számítási feladatokat. Ma már talán érdekesnek tűnik, miért is okozott sokaknak komoly problémát ez a kis változtatás. 2 + 6 = x y = 3 * 4
Változók Az Objective C szintén változókat használ. A változó nem más, mint egy meghatározott adat, például egy szám neve, amivel hivatkozni tudunk rá. Íme egy Objective C utasítás, egy egyszerű programsor, ahol egy változónak konkrét értéket adunk. //[1] x = 4;
A pontosvessző Itt az x nevű változónak a 4 értéket adjuk. Fontos megfigyelni, hogy az utasítás végén pontosvessző van. Minden utasítás végére pontosvesszőt kell tenni. Miért van erre szükség? Nos az [1]-es példában látható kódrészlet -6-
nyilvánvalónak tűnhet számodra, de a számítógép ezt még közvetlenül nem érti meg. Egy segédprogram, amit fordítóprogramnak nevezünk, lefordítja a szükséges 0-kból és 1-esekből álló utasításokra, amelyek már érthetők a Mac számára is. Az ember által beírt utasítások olvasása és értelmezése komplikált feladat a fordítóprogram számára, szükség van arra, hogy bizonyos jelölésekkel segítsünk neki. Ilyen például az, hogy az utasításokat pontosvesszővel zárjuk le. Ha elfelejted a pontosvesszőt kitenni, akkor a fordítóprogram nem tudja lefordítani a programkódot, abból bizony nem lesz a Mac által futtatható változat. Azért nem kell emiatt nagyon aggódni, mert a fordítóprogram panaszkodni fog, ha nem tudja értelmezni az általad beírt kódot. Ahogy a későbbiekben látni fogjuk, még abban is segít, hogy megpróbálja kitalálni, hogy hol van a hiba.
Változónevek Ugyan a változónevek teljesen közömbösek a fordítóprogram számára, mégis érdemes beszédes változóneveket használni. Ez sokat segíthet akkor, ha szeretnénk megérteni egy programkódot, vagy éppen hibakeresés közben is. Hagyományosan bug-nak (bogár, poloska) nevezünk egy programhibát és innen származik a debugging elnevezés a hibakeresésre és a hibák javítására. Ezért nem javasoljuk az olyan egyszerű és nem túl beszédes változónevek használatát, mint például az x. Ha mondjuk egy kép szélességére szeretnénk utalni, akkor használjuk a pictureWidth [2] elnevezést (pictureWidth = képszélesség). //[2] pictureWidth = 8;
Azt már láttuk, hogy a fordítóprogram számára már egy apró pontosvessző is milyen fontos. Meg kell még ismernünk néhány hasonlóan egyszerű szabályt, mint például azt, hogy a programkód érzékeny a kis és nagy betűk használatára. Tehát megkülönbözteti a kis és a nagy betűket. A pictureWidth változónév különbözik a pictureWIDTH-től, vagy a PictureWidth-től. Egy általánosan elfogadott konvenció alapján a változónevekben több szót is egybeírhatunk, az elsőt kis betűvel, míg a többi szót nagy betűvel kezdve, ahogy azt a [2]-es példában már láttuk. Ezt a stílust teveEsetnek is szokták nevezni. Ha ehhez hozzászokik valaki és következetesen alkalmazza, akkor jelentősen csökkenni fognak a kis és nagy betűk felcseréléséből adódó programhibák. Jegyezd meg, hogy a változónevek mindig egyetlen szóból állnak, tehát szóközt nem tartalmazhatnak. Ugyan bőséges szabadsággal rendelkezel a változónevek megválasztásának területén, azért van néhány szabály amit figyelembe kell venni. Ezen a ponton eléggé unalmas lenne ezek felsorolása, de a legfontosabb szabályt feltétlenül jegyezd már most meg: a változónév nem lehet egy Objective C által lefoglalt szó, tehát olyan, amelyik speciális jelentéssel bír az Objective C számára. Amennyiben a pictureWidth-hez hasonlóan a fenti konvenciónak -7-
megfelelő összetett és beszédes változóneveket használsz, biztonságban érezheted magadat. Tehát mégegyszer összefoglalva: válassz beszédes változóneveket, használj nagy betűket közben, és biztosan kevesebb hibád lesz a programozás során. Befejezésül még két dolog: a változónév tartalmazhat számokat is, de azzal nem kezdődhet. Ugyanakkor az aláhúzás karakterrel kezdődhet. Íme néhány példa és ellenpélda: Korrekt váltózónevek: door8k do8or do_or Nem megengedett változónevek: door 8 (szóközt tartalmaz!) 8door (számjeggyel kezdődik) Nem javasolt változónév: Door8 (nagy betűvel kezdődik)
Változók használata számítási feladatokban Miután már tudjuk, hogyan kell egy változónak értéket adni, most műveleteket fogunk végezni vele. Az alábbi [3]-as programrészlet egy kép területének kiszámítását mutatja be. //[3] pictureWidth=8; pictureHeight=6; pictureSurfaceArea=pictureWidth*pictureHeight;
Szerencsére a fordítóprogram nem törődik az üres karakterekkel (eltekintve a változónevek, kulcsszavak, ... stb. belsejétől), ezért nyugodtan írhatjuk a kódot szelősebben, a szemnek könnyebben olvasható formában. //[4] pictureWidth = 8; pictureHeight = 6; pictureSurfaceArea = pictureWidth * pictureHeight;
Egész és valós típusú válozók Most vessünk egy pillantást az [5]-ös példára, annak is az első két utasítására. -8-
//[5] pictureWidth = 8; //[5.1] pictureHeight = 4.5; //[5.2] pictureSurfaceArea = pictureWidth * pictureHeight;
A számokat alapvetően két típusba sorolhatjuk: egész számok és tört számok. Mindegyikre mutatunk egy példát, lásd [5.1] és [5.2]. Az egész számokat elsősorban számolásra használjuk, amikor meghatározott alkalommal szeretnénk adott műveletet megismételni (részletesebben ezzel a 7. fejezetben fogunk foglalkozni). A törtszámok, vagy lebegőpontos számok például különböző sportágak átlagpontszámai alapján lehetnek ismerősek. Az [5]-ös programrészlet nem lesz működőképes. A problémát itt az okozza, hogy a fordítóprogram megköveteli, hogy előre megmondjuk milyen változóneveket fogunk használni és azt is, hogy azokhoz milyen típusú adatok tartoznak, tehát egészek, vagy lebegőpontos számok. Ezt nevezzük a változók deklarásának. //[6] int pictureWidth; //[6.1] float pictureHeight, pictureSurfaceArea; //[6.2] pictureWidth = 8; pictureHeight = 4.5; pictureSurfaceArea = pictureWidth * pictureHeight;
A [6.1] sorban az int azt mutatja, hogy a pictureWidth változó egész típusú. A következő sorban egyszerre két változót is deklarálunk, neveiket egymástól vesszővel választottuk el. Egész pontosan a [6.2] utasítás szerint mindkét változó float típusú, azaz úgynevezett lebegőpontos számok, amelyek egész és törtrészt is tartalmaznak. Figyeljük meg, hogy példánkban a pictureWidth változó típusa különbözik a másik két változó típusától. Az is megfigyelhető, hogy ha összeszorzunk egy int típust egy float típussal, akkor a művelet eredménye float lesz. Ezért kell a pictureSurfaceArea változót is float típusúnak deklarálni [6.2]. Miért kell a fordítóprogramnak előre tudni, hogy egy változó egész típusú lesz, vagy törtrészeket is tartalmazni fog? Nos egy számítógépes program használni fogja a gép memóriájának egy részét. A fordítóprogram feladata, hogy lefoglalja a megfelelő memória részt (byte-okat) az összes változó számára. Mivel különböző típusú adatok, esetünkben int és float típusok, különböző memória szeletet és ábrázolást igényelnek, a fordítóprogramnak a korrekt memória területet kell beállítania és a korrekt ábrázolást kell használnia. Felvetődik a kérdés, hogy mi történik, ha nagyon nagy számokkal, vagy igen nagy pontosságú tizedes számokkal kell dolgoznunk? Ezek ábrázolására esetleg nem lesz elegendő az a néhány byte, amit a fordítóprogram az eddigi példákban lefoglalt. A válasz kettős erre a kérdésre. Egyrészt az int és float típusoknak van olyan megfelelője, amelyik nagyobb méretű, illetve nagyobb pontosságú számok ábrázolására alkalmas. A legtöbb rendszeren megtalálható a megfelelő long long és a double típus. De ez elvezet a kérdés másik szintjéhez is bennünket. Egy programozó feladata, hogy az adott problémához megtalálja a legjobb megoldást. Ennek részletesebb tárgyalása azonban nem egy bevezető könyv első fejezetének témája. Megjegyezzük, hogy az egész számok és a tizedes számok is lehetnek negatív előjelűek is, ahogy az ismerős lehet például a bankszámláddal kapcsolatban. Amennyiben biztos vagy -9-
abban, hogy egy változó értéke nem lehet negatív, akkor lehetőséged van jobb memória kihasználásra. //[7] unsigned int chocolateBarsInStock;
Nincs sok értelme negatív számú csokoládéről beszélni az üzlet polcán, ezért egy unsigned int változót használhatunk ebben az esetben. Ahol az unsigned int típus olyan egész számot reprezentál, amelyik nulla, vagy pozitív. Változó deklarálás Menet közben is lehet változókat deklarálni [8]. //[8] int x = 10; float y= 3.5, z = 42;
Ezzel meg lehet spórolni egy kis gépelést. Adattípusok Ahogy azt láttuk, a változók által tárolt adatok különböző típusúak lehetnek, mint például int, vagy float. Az Objective-C egyszerű adattípusait skaláris adatnak nevezzük. A gyakori skaláris adattípusok a következők:
NÉV TÍPUS PÉLDA void Void üres int Egész ...-1, 0, 1, 2... unsigned Előjel nélküli egész 0, 1, 2... float Lebegőpontos -0.333, 0.5, 1.223, 202.85556 double Duplapontosságú lebegőpontos 0.52525252333234093890324592793021 char Karakter hello BOOL Logikai 0, 1; TRUE, FALSE; YES, NO.
Matematikai műveletek Az előző példákban már használtunk matematikai műveleteket. Az alapvető matematikai műveletek (operátorok) jelölése a következő: + összeadás - kivonás
- 10 -
/ osztás * szorzás
Az operátorok segítségével nagyon sokfajta számolási feladatot tudunk elvégezni. Professzionális Objectice C programozók által írt kódokban sok rövidítést lehet találni, talán ez abból következik, hogy nem nagyon szeretnek sokat gépelni. Ahelyett, hogy azt írnák, hogy x = x + 1; a programozók gyakran használják a [9]-es, vagy [10]-es változatot. //[9] x++; //[10] ++x;
Mindkét esetben az a feladat, hogy növeljük meg az x értékét 1-gyel. Azonban bizonyos körülmények között lényeges lehet, hogy a ++ jel a név előtt, vagy mögötte áll. Nézzük meg a [11]-es és [12]-es példákat. //[11] x = 10; y = 2 * (x++); // az x értéke 11 a művelet elvégzése UTÁN lett megnövelve // az y értéke 20
//[12] x = 10; y = 2 * (++x); //[12.2] // az x értéke most is 11 és már ez volt az értéke a művelet elvégzése ELŐTT is // az y értéke 22
A [11]-es példában a műveletek elvégzése után az y értéke 20, az x értéke pedig 11. Ezzel szemben a [12.2] utasításban az x értékét megnöveljük 1-gyel mielőtt még a 2-essel való szorzást elvégeznénk. Ezért a végeredményben az x értéke most is 11 lesz, de az y értéke ebben a változatban 22 lesz. A [12]-es kód iegyenértékű a [13]-assal. //[13] x = 10; x++; //[13.2] y = 2 * x; // ebben a változatban
a
[13.2] sorban a ++x, vagy az x++ felcserélhető
Láthatjuk, hogy időnkét lehetőség van arra, hogy két utasítást összevonjunk, és helyettük csak egyet írjunk le. Személy szerint nekem erről az a véleményem, hogy ezáltal a program olvashatósága, áttekinthetősége lecsökken. Megfelelő billentyűkombinációk beállítása esetén a programírás közben kényelmes lehet, de jelentősen megnöveli a programhibák előfordulási lehetőségét. Zárójelek
- 11 -
Emlékeztetnénk régi kedves iskolai emlékeidre, mely szerint a zárójelekkel felül lehet írni a műveletek sorrendjét. Ez így van a számítástechnikában is. A szorzás és az osztás magasabb rendű művelet, mint az összeadás és a kivonás. Ezért a 2 * 3 + 4 egyenlő 10-zel. A zárójelek használatával viszont elérhető, hogy az összeadás művelete legyen először elvégezve, ami miatt a 2 * (3 + 4) művelet eredménye 14 lesz. Osztás Az osztás művelete megkülönböztetett figyelmet érdemel, ugyanis másképpen működik amikor egész számokon, illetve amikor lebegőpontos számokon operál. Vessünk egy pillantást a következő két példára [14], [15]. //[14] int x = 5, y = 12, ratio; ratio = y / x; //[15] float x = 5, y = 12, ratio; ratio = y / x;
Az első esetben [14] az eredmény 2. Csak a második esetben [15] kapjuk a 2.4-et, amire valószínűleg számítottál. Logikai típus A Boolean egyszerűen egy igaz (True), vagy hamis (False) érték. Az 1 és a 0 megfelel az igaz és a hamis értéknek. A két jelölés felcserélhető, egymással ekvivalens: True False 1 0 Gyakran használják ezt a típust annak eldöntésére, hogy egy lépés elvégzésre kerüljön-e annak függvényében, hogy egy boolean típusú változó, vagy függvény értéke igaz, vagy hamis. Modulus A modulus képzés műveletének a jele: % (modulus). Elképzelhető, hogy ez nem annyira ismerős számodra, kicsit más eredményt adhat, mint amire számít az ember. Nem egy százalékot számoló operátorról van most szó. A % operátor egy egész tipusú osztás segítségével elosztja az első operandust a másodikkal és az eredmény a maradék. Amennyiben a második operandus 0, a művelet nincs értelmezve. //[16] int x = 13, y = 5, remainder; remainder = x % y;
- 12 -
Mivel az 5 csak 2-szer van meg a 13-ban (5 * 2 = 10), a maradék 3 (13 - 10 = 3) és ez lesz a művelet eredménye. Álljon itt még néhány példa modulus képzésre: 21 22 23 24 27 30 31 32 33 34 50 60
% % % % % % % % % % % %
7 = 0 7 = 1 7 = 2 7 = 3 7 = 6 2 = 0 2 = 1 2 = 0 2 = 1 2 = 0 9 = 5 29 = 2
Bizonyos esetekben nagyon kényelmesen lehet vele dolgozni, de fontos megjegyezni, hogy csak egész típusú változókkal működik. Egy gyakori alkalmazási területe a modulus használatának annak az eldöntése, hogy egy egész szám páros, vagy páratlan. Amennyiben páros, akkor a 2-es modulusa 0, egyébként pedig nem. Például: //[17] int anInt; //valamilyen kód, amelyik beállatja az if ((anInt % 2) == 0) { NSLog(@"az anInt páros"); } else { NSLog(@"az anInt páratlan"); }
anInt változó értékét
- 13 -
02: Nincs megjegyzés? Ez elfogadhatatlan! BecomeAnXcoder - 02: No comment? Unacceptable!
Nincs megjegyzés? Ez elfogadhatatlan!
Bevezető Beszédes változóneveket használva a programjaink olvashatóbbak és érthetőbbek lesznek [1]. //[1] float pictureWidth, pictureHeight, pictureSurfaceArea; pictureWidth = 8.0; pictureHeight = 4.5; pictureSurfaceArea = pictureWidth * pictureHeight;
Bár eddigi példáink csak néhány soros programok voltak, de még egy egyszerű program is könnyen több száz, vagy akár több ezer soros programmá is kinőheti magát és ha pár héttel, vagy hónappal később ismét elővesszük a kódot, nem biztos, hogy minden részletére pontosan emlékezni fogunk, hogy mit miért csináltunk. De erre valók a programkódba illesztett megjegyzések. A megjegyzések sokat segítenek abban, hogy gyorsan megértsük az egyes programrészletek feladatát. Vannak olyan programozók akik egyenesen megjegyzések írásával kezdik a programírást, azok segítségével állítják fel a program szerkezetét, ami egy könnyen áttekinthető kódot eredményez. Feltétlenül javasoljuk, hogy szánjál egy kis időt arra, hogy megjegyzéseket helyezz el a programkódba. Garantáljuk hogy ezzel sok időt megspórolsz magadnak a későbbiekben, ha majd ismét előveszed a kódodat. De akkor is nagyon hasznos, ha megosztod másokkal a munkádat és ők szeretnék azt beilleszteni a saját kódjukba gyorsan és egyszerűen. Megjegyzések írása A megjegyzéseket két egymás melletti törtvonallal kell kezdeni a kódban. // Ez egy megjegyzés
Az Xcode-ban a megjegyzések zöld színben jelennek meg. Ha hosszú, többsoros megjegyzést szeretnénk elhelyezni, akkor /* */ jelek közé is lehet írni. /* Ez egy több soros megjegyzés */
Kikommentezés - 14 -
Hamarosan tárgyalni fogjuk a hibakeresést és bemutatjuk, hogy az Xcode milyen nagyszerű lehetőségeket biztosít számunkra ezen a területen. A kikommentezés egy régi hibakeresési technika. A programkód egy részét a /* */ jelekkel megjegyzéssé változtatjuk, azaz kikommentezzük, amivel az adott programrészletet ideiglenesen kiiktatjuk. Így ellenőrizni tudjuk, hogy a maradék program elvárásainknak megfelelően működik-e. Ez segíthet abban, hogy levadásszuk a hibás részt. Amennyiben az így kiiktatott rész aktív, például értéket ad egy változónak, akkor beilleszthetünk egy ideiglenes sort ahol beállítjuk a változó szükséges értékét. Miért írjunk megjegyzéseket? A megjegyzések fontosságát nehéz eltúlozni. Gyakran hasznos ha néhány emberi mondatban elmagyarázzuk, hogy egy kódrészlet valójában mire is való. Ha nem kell időt töltened azzal, hogy kitalálod a programrész működését, sokkal gyorsabban el tudod dönteni, hogy a keresett probléma az adott részben van, vagy valahol máshol kell azt keresni. A megjegyzések segíthetnek kitalálni olyan dolgokat is amikre közvetlenül nagyon nehéz, vagy szinte lehetetlen rájönni. Például ha a program egy olyan matematikai modellt használ, amelyik kevésbé közismert, akkor célszerű elhelyezni egy irodalmi utalást arra, hol lehet megtalálni a szükséges elméleti alapokat. Néha hasznos lehet már a programozás kezdetén megjegyzések segítségével kialakítani a program szerkezetét. Ez segít a program strukturálásában és megkönnyíti a programfejlesztést a későbbiekben. Ebben a könyvben található programkódok nem tartalmaznak annyi megjegyzést, mint amennyit el szoktunk helyezni a programjainkban. Ez csak azért van így, mert a környező szövegek segítségével is magyarázzuk azok jelentését.
- 15 -
03: Függvények BecomeAnXcoder - 03: Functions
Függvények
Bevezető Az eddigi leghosszabb programkód, amivel találkoztunk, csupán 5 soros volt. De a több ezer soros programok sem ritkák, megértésük nem egyszerű feladat, ezért már a kezdeti lépéseknél meg kell ismerkednünk az Objective C programok felépítésével. Ha egy program utasítások végeláthatatlan sorozata lenne, akkor az nagyon megnehezítené a hibakeresést. Az is előfordulhat, hogy bizonyos programrészek többször előfordulnak egy programban. Ha itt valahol hiba van, akkor minden egyes előfordulás során ki kell javítani azt. Ez egy rémálom lehet, elég csak elfelejteni egy, vagy több részletet kijavítani! Keresni kell tehát azokat a lehetőségeket, amelyek segítségével áttekinthetőbbé tehetjük a programunkat, könnyebbé a hibakeresést. A probléma megoldása abban rejlik, hogy feladatuk szerint csoportosítsuk az utasításokat. Megmutatjuk ezt egy példán. Tegyük fel, hogy van egy utasításkészletünk a kör kerületének kiszámítására. Ha egyszer már letesztelted, hogy ez a kódrészlet helyesen működik, akkor a későbbiekben már nem lesz szükség arra, hogy ebben a kódrészletben hibát keressél. Ezt az utasításkészletet függvénynek fogjuk nevezni, nevet adunk neki és a neve alapján tudunk majd hivatkozni rá, azaz meghívjuk a függvényt. A függvények használata annyira alapvető, hogy legalább egy függvény garantáltan van minden programban: ez pedig a main() függvény. A main() függvényt keresi a fordítóprogram először, ez mutatja meg számára azt a kódot amit a program indításakor értelmeznie kell. A main() függvény Nézzük meg a main() függvény felépítését részletesebben. [1] //[1] main() //[1.1] { //[1.3] // A main() függvény törzse. Ide kell beilleszteni a programkódot. } //[1.5]
Az [1.1] mutatja a függvény nevét, jelen esetben ez a "main", amit egy nyitó és egy záró zárójel követ. A "main" egy foglalt név és ahogy említettük, a main() függvény minden programban kötelezően jelen van. Amikor saját függvényeket definiálsz, szinte semmilyen megkötés sincs arra, hogyan fogod azt elnevezni. A zárójelek fontos szerepet játszanak, de erről majd egy kicsit később fogunk beszélni ebben a fejezetben. Az [1.3, 1.5] sorokban - 16 -
kapcsos zárójeleket láthatunk. Ezek közé a kapcsos zárójelek közé kell írnunk a programkódot: { }. A kapcsos zárójelek közötti részt a függvény törzsének nevezzük. Az első fejezetből átvett megfelelő kódrészletet helyeztünk el a [2]-es példában. //[2] main() { // Változó deklarálások float pictureWidth, pictureHeight, pictureSurfaceArea; // Inicializáljuk a változókat, értéket adunk nekik pictureWidth = 8.0; pictureHeight = 4.5; // Itt végezzük el az aktuális számítási feladatot pictureSurfaceArea = pictureWidth * pictureHeight; }
Az első függvényünk Amennyiben az összes programkódot a main() függvénybe helyeznénk, akkor éppen abba a csapdába kerülnénk, amitől szerettük volna megóvni magunkat, egy strukturálatlan, nehezen kezelhető programkódot kapnánk. Ehelyett strukturáljuk a programunkat, és a main() program mintájára készítsünk egy circleArea() függvényt [3]. //[3] main() { float pictureWidth, pictureHeight, pictureSurfaceArea; pictureWidth = 8.0; pictureHeight = 4.5; pictureSurfaceArea = pictureWidth * pictureHeight; } circleArea() // [3.9] { }
Idáig ebben nem volt semmi ördöngösség, de a [3.9] sorban kezdődő új függvény egyelőre nem csinál semmit. Figyeljük meg, hogy az új függvény specifikációja a main() függvény törzsén kívül helyezkedik el. Másképpen megfogalmazva, ezek a függvények nincsenek egymásba skatulyázva. Az új circleArea() függvényt a main() belsejéből kell meghívni, Nézzük meg hogyan [4]. //[4] main() { float pictureWidth, pictureHeight, pictureSurfaceArea, circleRadius, circleSurfaceArea; // [4.4] pictureWidth = 8.0; pictureHeight = 4.5; circleRadius = 5.0; // [4.7] pictureSurfaceArea = pictureWidth * pictureHeight; // Itt hívjuk meg az új függvényt! circleSurfaceArea = circleArea(circleRadius); // [4.10] }
Megjegyzés: a program egésze itt nem látható (lásd [3]).
- 17 -
Paraméterátadás Definiáltunk float típusú változókat [4.4], inicializáltuk a circleRadius változót, értéket adtunk neki [4.7]. A legérdekesebb a [4.10]-es sor, ahol a circleArea() függvényt meghívtuk. Ahogy látható, a circleRadius változónevet zárójelek közé tettük. Ez a circleArea() függvény argumentuma. A circleRadius változó értéke itt kerül átadásra a circleArea() függvény részére. Amikor a circleArea() függvény elvégzi a feladatát, azaz kiszámolja a kör kerületét, visszatér az eredménnyel. Ehhez még ki kell egészíteni a circleArea() függvényt [3] a megfelelő utasításokkal [5]. Megjegyzés: itt most csak a circleArea() függvény látható. //[5] circleArea(float theRadius) // [5.1] { float theArea; // [5.1] theArea = 3.1416 * theRadius * theRadius; // a sugár négyzetét megszorozzuk a pi számmal [5.4] return theArea; // [5.6] }
Az [5.1] sorban definiáljuk, hogy a circleArea() függvény számára egy float típusú bemenetre van szükség. Amikor ezt megkapja, a theRadius nevű változóba kerül a bemeneti érték. Még egy változóra lesz szükségünk: theArea, ahol a számítás eredményét fogjuk tárolni [5.4], ezért ezt is deklarálnunk kell [5.3]. Ugyanúgy tesszük ezt ahogy a változókat deklaráltunk a main() függvényben [4.4]. Érdemes megfigyelni, hogy a theRadius változó deklarálása a zárójelek között történik [5.1]. Az [5.6] sorban az eredmény visszatér abba a sorba, ahonnan a függvény meg lett hívva. Ezért az eredmény a [4.10] sorban a circleSurfaceArea változó értéke lesz. Már majdnem teljesen készen vagyunk az [5]-ös példában bemutatott függvénnyel kapcsolatban, egy dolog azért még hiányzik. Nem határoztuk meg a függvény visszatérési értékének típusát. A fordítóprogram megköveteli ezt tőlünk, ezért nem tehetünk mást, mint beállítjuk azt float típusra [6.1]. //[6] float circleArea(float theRadius) //[6.1] { float theArea; theArea = 3.1416 * theRadius * theRadius; return theArea; }
A [6.1] sor megadja, hogy a függvény visszatérési értéke, azaz a theArea változó értékének a típusa float. A programozónak garantálnia kell, hogy a circleSurfaceArea változó a main() függvényben [4.4] ugyanilyen típusra legyen definiálva, és akkor a fordítóprogram nem zsémbeskedhet velünk. Nincs minden függvénynek argumentuma. Ha nincs, a zárójelekre () akkor is szükség van, bár ekkor közöttük nincs semmi. //[7] int throwDice() { int noOfEyes; // Ez egy 1 és 6 közötti véletlenszámot generáló programkód
- 18 -
return noOfEyes; }
Visszatérési értékek Olyan függvény is van, aminek nincs visszatérési értéke. Az ilyen függvény típusa: void. Ebben az esetben a return utasítás opcionális. Ha használod a return utasítást, akkor azt nem követheti érték, vagy változónév ebben az esetben. //[8] void beepXTimes(int x); { // Ide egy olyan programkód kerül, amelyik x alkalommal sípol return; }
Lehetséges, hogy egy függvény egynél több argumentummal is rendelkezik, ilyen a pictureSurfaceArea() függvény. ilyen esetben az argumentumokat vesszővel kell elválasztani egymástól. //[9] float pictureSurfaceArea(float theWidth, float theHeight) { // területszámító programkód }
Megállapodás szerint a main() függvény visszatérési értékének egy integer-nek kell lenni a következő jelentéssel. A 0 (nulla, [10.9]) visszatérési érték jelentése az, hogy a program probléma nélkül lefutott. Mivel a main() függvény egy egész értékkel tér vissza, ezért az "int" kódot kell írnunk a main() rész elé [10.1]. Tekintsük át egyben eddigi programunkat! //[10] int main() //[10.1] { float pictureWidth, pictureHeight, pictureSurfaceArea, circleRadius, circleSurfaceArea; pictureWidth = 8; pictureHeight = 4.5; circleRadius = 5.0; pictureSurfaceArea = pictureWidth * pictureHeight; circleSurfaceArea = circleArea(circleRadius); // [10.8] return 0; // [10.9] } float circleArea(float theRadius) // [10.12] { float theArea;// [10.14] theArea = 3.1416 * theRadius * theRadius; return theArea; }
- 19 -
Hozzuk működésbe a dolgokat A [10]-es példában láthatjuk, hogy van egy main() függvényünk [10.1] és egy másik, általunk definiált függvényünk is [10.12]. Amennyiben megpróbáljuk lefordítani ezt a kódot a fordítóprogram még mindig akadályba ütközik. A [10.8]-as sorban nem fogja megérteni a circleArea() függvényhívást. Vajon miért? Valóban, a fordítóprogram elkezdi értelmezni a main() függvényt és egyszer csak egy olyan részt talál benne, ami ismeretlen számára. Megáll ezen a ponton, nem keres tovább, csupán egy figyelmeztetést küld. Nagyon egyszerűen segíthetünk ezen a problémán azzal, hogy deklaráljuk ezt a függvényt még az int main() utasítás előtt [11.1]. Nincs ebben semmi nehézség, ez pontosan ugyanolyan, mint a [10.12]-es sor, attól, csak egy picit tér el: pontosvessző van a végén. Ezután a fordítóprogram már nem fog meglepődni, amikor ezzel a függvényhívással találkozik. //[11] float circleArea(float theRadius); // [11.1] int main() { // a Main függvény kódja kerül ide ... }
függvény deklaráció
Megjegyzés: a teljes programkód itt nem látható (lásd [10]). Hamarosan valóságban is le fogjuk fordítani ezt a programot, de előtte még álljon itt pár megjegyzés. A programíráskor tartsuk szem előtt, hogy egy kódrészletre a későbbiekben is szükségünk lehet majd. Beilleszthetünk egy rectangleArea() függvényt, ahogy a [12]-es példában látjuk, és ezt a függvényt meghívhatjuk a main() függvényben. Még akkor is hasznos lehet az, ha egy programrészletet egy függvénybe helyezünk, amennyiben azt csupán egyszer használjuk fel. A main() könnyebben olvasható ezáltal. A hibakeresés során könnyebben megtalálhatjuk a hibás részt ezen a módon, mert lehetséges, hogy nem kell végignézni utasítások hosszú sorozatát, hanem ehelyett esetleg elegendő csak a függvényben található néhány utasítást áttanulmányozni, amiket könnyű megtalálni köszönhetően a nyitó és záró kapcsos zárójeleknek. //[12] float rectangleArea(float length, float width) { return (length * width); //[12.3] }
//[12.1]
Ebben a példában azt is bemutatjuk, hogyan lehet egy egyszerű utasítással egy sorban elvégezni a számítási feladatot és rögtön visszaadni annak az eredményét [12.3]. A [10.14] sorban a theArea változó ezért valójában felesleges, csak arra használtuk, hogy bemutassuk a változó deklarálást a függvényen belül. Már ezeken az egyszerű példákon keresztül is nagyon jól látható, hogy ha nem változtatjuk - 20 -
meg a függvény deklarációját (azaz az első sorát), akkor ugyanúgy tudunk rá hivatkozni akkor is ha a függvény programkódját megváltoztatjuk. Például megváltoztathatjuk egy változó nevét a függvényen belül, és a függvény ugyanúgy fog működni és ez egyáltalán nem érinti a program többi részét. Ez azt is jelenti, hogy valaki más meg tudja írni a függvényt és te tudod azt használni anélkül, hogy ismernéd annak a belső felépítését. Csak annyit kell tudnod, hogy hogyan kell használni a függvényt. Tehát ismerned kell a: - függvény nevét - a függvény argumentumainak számát, sorrendjét és típusát - mi a függvény visszatérési értéke (pl. a téglalap területének értéke) és az eredmény típusát A [12]-es példában ezek rendre: - rectangleArea - két argument, mindkettő float, ahol az első a hosszúságot, a második a szélességet jelöli - a függvénynek van visszatérési értéke, aminek a típusa float (ami az utasítás első szavából látható) [12.1]).
Rejtett változók Érdemes megjegyezni, hogy a függvény belseje láthatatlan a main programból és más függvényekből is. Ez azt jelenti, hogy egy függvényben a változók értékei nincsenek kapcsolatban más függvények változóival, még akkor sem, ha ugyanaz a nevük. Ez az Objective C egyik legalapvetőbb jellegzetessége, amit az 5. fejezetben tovább fogunk tanulmányozni. De most már ideje, hogy futtassuk a [10]-es példában szereplő programot.
- 21 -
04: Kiiratás a képernyőre BecomeAnXcoder - 04: Printing on screen
Kiiratás a képernyőre
Bevezető Az eddigiekben szépen felépítettük a programunkat, de nem mondtuk semmit arról, hogyan lehet a képernyőn megjeleníteni az eredményeket. Az Objective C önmagában nem képes erre, de szerencsére vannak olyan háttérfüggvények, amelyek segítségünkre lesznek. Több lehetőség is létezik, mi most Az Apple Cocoa környezet által tartalmazott beépített függvényt fogjuk használni erre a célra, az NSLog() függvényt. Ez nagyon kényelmes és egyszerű lesz, még programozni sem kell hozzá semmit. Tehát hogyan is működik ez az NSLog() függvény? Az Xcode rendelkezik egy Console ablakkal, ahol ki tudja írni a napló üzeneteket. A Console ablakot úgy tudjuk megnyitni, ha a Run menüből kiválasztjuk a Console sort (Cmd-shift-R). Egy program futtatása során a program összes NSLog() üzenete látható lesz ebben az ablakban. Az NSLog() alapvetően arra lett tervezve, hogy a hibaüzeneteket írja ki és nem a program eredményeit. Azonban annyira egyszerű a használata, hogy ebben a könyvben a segítségével fogjuk megjeleníteni az eredményeket. Minél többet tudsz majd a Cocoa környezetről, annál kifinomultabb módszerek állnak majd a rendelkezésedre az eredmények kiíratására.
Az NSLog használata Most megmutatjuk, hogyan kell használni az NSLog() függvényt. A main.m fájlba gépeld be a következő sorokat: //[1] int main() { NSLog(@"Julia a kedvenc színésznőm."); return 0; }
Az [1] programkód futtatásakor a "Julia a kedvenc színésznőm." szövegnek kell megjelennie a képernyőn. A @" és " jelek közötti részt stringnek nevezzük. - 22 -
A szöveg kiíratása mellett az NSLog() függvény még további információkat is kiír a képernyőre, mint például az aktuális időpontot,vagy a program nevét. Egy lehetséges eredményre mutatunk most példát: 2005-12-22 17:39:23.084 test[399] Julia a kedvenc színésznőm.
A string hossza lehet nulla, vagy 1, vagy 1-nél több karakter. Megjegyzés: A következő példákban, csak a main() főggvény törzsének megfelelő utasításait adjuk meg. //[2] NSLog(@""); NSLog(@" ");
//[2.1] //[2.2]
A [2.1] utasítás egy üres stringet mutat be, ennek a hossza nulla karakter, amit úgy is szoktunk mondani, hogy nulla karaktert tartalmaz. A [2.2] utasítás viszont már nem egy üres string, annak ellenére, hogy nagyon úgy néz ki. Ez tartalmaz egy üres karaktert (space karaktert) és ezért a hossza 1. Néhány karaktersorozat speciális jelentéssel bír egy stringben. Ezeket a speciális karaktereket vezérlő, vagy escape karaktereknek szokták nevezni. Annak érdekében, hogy az utolsó szó a mondatunkból a következő sorba kerüljön, egy speciáls kódot kell beilleszteni a [3.1] utasítás belsejébe. Ez a kód a következő: \n, egy új sor kezdését jelöli. //[3] NSLog(@"Julia a kedvenc \nszínésznőm.");
//[3.1]
Most a nyomtatási kép így néz ki (csak a megfelelő részt mutatjuk): Julia a kedvenc színésznőm.
A [3.1] utasításban a fordított törtvonal (visszafelé perjel) egy escape karakter, aminek hatásárára az NSLog() függvény tudni fogja, hogy az ezt követő karakter egy speciális karakter, amit nem kell kiíratni a képernyőre, hanem speciális jelentése van. Ebben az esetben az "n" azt jelenti, hogy "kezdj egy új sort" (new line). Abban a ritka esetben, ha éppen egy fordított törtvonalat szeretnénk kiíratni a képernyőre, az bizony első pillanatban gondot okozhat. A megoldás azonban nagyon egyszerű. Írjunk egymás mellé két fordított törtvonalat. Az első megváltoztatja a második jelentését, azaz megszünteti annak escape karakter voltát. Ebből az NSLog() függvény tudni fogja, hogy a második fordított törtvonalat már ki kell nyomtatni és a speciális jelentését ignorálni kell. A következő példából minden világos lesz: //[4] NSLog(@"Julia a kedvenc színésznőm.\\n");
//[4.1]
A [4.1] utasítás hatására a képernyőn a következőt fogjuk látni Julia a kedvenc színésznőm.\n
- 23 -
Változók értékének kiíratása Idáig csak stringeket írattunk ki a képernyőre. Írassuk ki most egy számítás eredményét. //[5] int x, integerToDisplay; x = 1; integerToDisplay = 5 + x; NSLog(@"Az integer típusú változó értéke %d.", integerToDisplay);
Figyeljük meg, hogy a zárójelek között van egy szöveg, egy vessző és egy változónév. A szöveg tartalmaz valami mulatságosat: %d. A fordított törtvonalhoz hasonlóan a százalékjelnek: % is speciális jelentése van. Ezt egy d betű követi (a tizedes szám - decimal number rövidítése ez). A futtatás során azon a helyen ahol a %d szerepel kiíratódik a vessző után szereplő változó értéke, tehát az integerToDisplay változó aktuális értéke. Az [5]-ös példa futási eredménye ez lesz: Az integer típusú változó értéke 6.
Ha egy lebegőpontos változó értékét szeretnénk kiíratni, akkor a %f kódot kell használnunk a %d helyett. //[6] float x, floatToDisplay; x = 12345.09876; floatToDisplay = x/3.1416; NSLog(@"A float típusú változó értéke %f.", floatToDisplay);
Te tudod meghatározni, hogy hány darab tizedesjegyet írjon ki a program. Ahhoz, hogy két tizedesjegy kerüljön kiíratásra a .2 kódot kell írni a % és az f betűk közé, ahogy a következő példában láthatod: //[7] float x, floatToDisplay; x = 12345.09876; floatToDisplay = x/3.1416; NSLog(@"A float típusú változó értéke %.2f.", floatToDisplay);
Kicsit később megtanuljuk, hogyan lehet egyszerűen ismételni utasításokat. Képzeljünk el például egy konverziós táblázatot a Celsius és a Kelvin fokok között. Ha szépen szeretnéd elrendezni a táblázatot, akkor jó lenne, ha két oszlopban és rögzített karakter szélességgel lennének kiírva a számok. Ezt könnyű beállítani egy egész szám segítségével, amit a % és az f betű (vagy a % és a d betű) közé kell beírni. Abban az esetben, ha kisebb számot adunk meg, mint a szám karakterhosszúsága, akkor az utóbbi élvez perefernciát. //[8] int x = 123456; NSLog(@"%2d", x); NSLog(@"%4d", x);;
//[8.2] //[8.3]
- 24 -
NSLog(@"%6d", x) NSLog(@"%8d", x);
//[8.5]
A [8]-as példa futtatási eredménye ilyen lesz: 123456 123456 123456 123456
Az első két utasításban [8.2, 8.3] túl kevés helyet igényelünk a számok kiíratásához, ezért a számok aktuális hossza érvényesül. Csak a [8.5] utasításban határozunk meg a szám hosszánál nagyobb értéket, ezért itt a szám előtt megjelennek a kiegészítő üres karakterek, és azokkal együtt lesz a karakterszám az előírtnak megfelelő. Természetesen kombinálni is lehet a kiíratandó szám szélességét és az elvárt tizedesjegyek számát. //[9] float x=1234.5678; NSLog(@"Lefoglalunk 10 karaktert a számnak és azt 2 tizedesjegy pontossággal írjuk ki."); NSLog(@"%10.2f", x);
Több érték együttes kiíratása Nincs akadálya annak sem, hogy egyszerre több változó értékét is kiírassuk, illetve annak, hogy keverjük a típusokat [10.3]. Figyelni kell arra, hogy helyesen válasszuk meg a típus meghatározást (int, float) a %d és az %f jelölések segítségével. //[10] int x = 8; float pi = 3.1416; NSLog(@"Az egész típusú változó értéke %d, a lebegőpontosé pedig %f.", x, pi); //[10.3]
A jelöléseknek és az értékeknek egyezniük kell Az egyik leggyakoribb hiba kezdő programozók esetében, hogy helytelenül adják meg az adat típusát az NSLog() és más függvényekben. Ha a kapott eredmény értelmetlen, vagy váratlanul lefagy a program futás közben, akkor érdemes azzal kezdeni a hibakeresést, hogy ellenőrizzük a típus beállításokat! Ha sikerül elrontani már az elsőt, akkor jó eséllyel a második sem fog helyesen megjelenni. Lássunk egy példát: //[10b] int x = 8; float pi = 3.1416; NSLog(@"Az egész típusú változó értéke pi);
%f, míg a lebegőpontosé %f.", x,
- 25 -
// A helyes változat ez lenne: NSLog(@"Az egész típusú változó értéke %d, míg a lebegőpontosé %f.", x, pi);
Ez a hibás kód a következő eredményt produkálja:
Az egész típusú változó értéke 0.000000, míg a lebegőpontosé 0.000000.
A Foundation könyvtár belinkelése Már csak egy kérdés és egy válasz maradt az első programmal kapcsolatban. Ez pedig nem más, mint hogy honnan tud a programunk erről a hasznos NSLog() függvényről? Hát bizony egyáltalán nem tud róla, egészen addig amíg nem segítünk neki ebben. A következő utasítás segítségével tudjuk beimportálni azt a könyvtárt, amelyik tartalmazza az NSLog() függvényt. Szerencsére ez a könyvtár a Macintosh operációs rendszer ingyenes tartozéka: #import
Ennek az utasításnak kell a legelsőnek lenni a programkódban. Az alábbi programkódban összefoglaltuk fejezetünk tartalmát. A következő fejezetben azt is megmutatjuk, hogyan kell azt futtatni. //[11] #import float circleArea(float theRadius); float rectangleArea(float width, float height); int main() { float pictureWidth, pictureHeight, pictureSurfaceArea, circleRadius, circleSurfaceArea; pictureWidth = 8.0; pictureHeight = 4.5; circleRadius = 5.0; pictureSurfaceArea = rectangleArea(pictureWidth, pictureHeight); circleSurfaceArea = circleArea(circleRadius); NSLog(@"A kör területe: %10.2f.", circleSurfaceArea); NSLog(@"A kép területe: %f. ", pictureSurfaceArea); return 0; } float circleArea(float theRadius) // az első saját függvény { float theArea; theArea = 3.1416 * theRadius * theRadius; return theArea; } float rectangleArea(float width, float height) // a második saját függvény { return width*height; }
- 26 -
05: Program fordítása és futtatása BecomeAnXcoder - 05: Compiling and Running a Program
Program fordítása és futtatása
Bevezető
A pogramkód, amit eddig alkottunk gyakorlatilag csak emberi olvasásra alkalmas szöveg. Mi sem sokat élvezünk belőle, de a Mac-nek még rosszabb, közvetelenül semmit sem tud kezdeni vele! Szüksége van egy speciális programra, amit fordítóprogramnak hívnak. Ez képes arra, hogy a programkódot átfordítsa gépi kódra, ami már értelmezhető, futtatható lesz a Mac-en. A fordítóprogram része az Apple ingyenes programfejlesztő környezetének. Telepíteni kell az Xcode programot a Mac OS X telepítő lemezéről, ha eddig ezt még nem tetted volna meg. Azt is érdemes ellenőrizni, hogy a legfrissebb változattal rendelkezel-e, amit a http://developer.apple.com oldalon tudsz megtenni (ingyenes regisztrációra szükség van). Projekt
Indítsuk el a Developer folderen belül található Application folderből az Xcode programot. Az első indítás alkalmával feltesz egy sor kérdést. Nyugodtan elfogadhatod a felajánlott válaszokat, mert azokkal el lehet kezdeni a munkát és később ha szükséges, bármelyiket egyszerűen meg tudod változtatni a Preferences-ben. Válasszuk ki az új projektet (New Project) a File menüből. A programban különböző projekt típusokból lehet választani.
- 27 -
Az Xcode Assistant segítségével kiválasztjuk az új projekt típusát Egy nagyon egyszerű Objective C program megírása a célunk, amelyik nem használ grafikus felhasználói felületet (GUI), ezért válasszuk ki a Foundation Tool-t a Command Line Utility csoportból.
- 28 -
A projekt nevének és elérési útvonalának beállítása. Legyen a program neve "justatry". Válasszuk, ki hova szeretnénk elmenteni és kattintsunk a Finish gombra. A programunkat közvetlenül a Terminal-ból is lehet futtatni. Amennyiben ezt szeretnéd és szeretnél elkerülni alapvető kellemetlenségeket, akkor a neve kezdődjön kis betűvel és csak egy szóból álljon. Nagy betűvel majd azoknak a programoknak a nevét fogjuk jelölni, amelyek már grafikus felhasználói felületet használnak.
Ismerkedés az Xcode-dal
Most egy olyan ablakot látsz, amivel a programozás során gyakran fogsz találkozni. Ez két fő részre bontható. A bal oldali "Groups & Files" nevű oszlopban a projekthez tartozó összes fájlt el lehet érni. Most még nincs olyan sok, de később, amikor többnyelvű grafikus programokat fogsz fejleszteni, akkor itt lesz majd látható a grafikus felhasználói felületet leíró - 29 -
és a nyelvi változatokat tartalmazó összes fájl. A fájlok úgynevezett virtuális folderekben vannak csoportosítva a könnyebb áttekinthetőség érdekében, de a Finderben nem fogod ezeket megtalálni, csupán az Xcode-on belüli tájékozódást könnyítik meg. A bal oldali listában kattintsunk a justatry-ra, majd a csoportba tartozó fájlok nevére kattintva megnézhetjük ezek forráskódját. Van itt egy justatry.m nevű fájl [1]. Emlékszel, hogy mindegyik programnak kell tartalmaznia egy main() függvényt? Nos ez az a fájl, amelyik tartalmazza a main() függvényt. Hamarosan módosítani fogjuk ezt és beillesztjük a saját pogramkódunkat. Ha duplán rákattintasz az ikonjára és kinyitod a justatry.m fájlt, meg fogsz lepődni. Az Apple már meg is csinálta helyetted a main() függvényt.
A main() függvény megjelenítése az Xcode-ban. //[1] #import int main (int argc, const char * argv[]) //[1.2] { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; //[1.4] // ide jön a programkód ... NSLog(@"Hello, World!"); [pool drain]; //[1.7]
- 30 -
return 0; }
Tanulmányozzuk a programkódot, és figyeljünk a következőkre: - Az NSLog() és más alapfüggvények meghívásához egy import utasítás szükséges, amit a # jellel kell kezdeni. - A main() függvény megléte. - A kapcsos zárójelek ölelik át a programtörzset. - Egy megjegyzés, ami jelen esetben megmutatja hova kell írni a programkódot. - Egy NSLog() utasítás, aminek segítségével kiíratunk egy stringet a képernyőre. - A return 0; utasítás. Van még néhány ismeretlen dolog is számodra itt: - Két fura argument a main() függvény zárójelei között [1.2] - Az NSAutoreleasePool kezdetű utasítás [1.4] - Egy másik utasítás, amelyik tartalmazza a pool és drain szavakat [1.7] Személy szerint nem szoktam örülni annak, amikor egy könyvben azt olvasom, hogy az itt szereplő dolgokat majd később meg fogom érteni. Ezért is tárgyaltuk olyan részletesen a függvény fogalmát az eddigiekben, hogy a lehető legtöbb dolgot előre tisztázzuk. Már pontosan tudod, hogy a függvények arra valók, hogy áttekinthetőbbé tegyék a programot, hogy minden programban van egy main() függvény, és azt is hogy milyen a függvények felépítése. Azonban el kell ismernem, hogy nem sikerült előre megmagyarázni mindent, ami az [1]-es példában szerepel. Valóban sajnálom, de kérem, hogy egyelőre tekintsünk el ezektől a dolgoktól (mint pl. [1.2, 1.4 és 1.7]). Először azokra az alapvető dolgokra szeretnénk koncentrálni az Objective C programozás tanítása során, amelyekre feltétlenül szükség van az egyszerű programok megírásához. Azért annyi jó hírrel szolgálhatunk, hogy két kemény fejezeten már túljutottál, és most három könnyebb lélegzetű következik mielőtt ismét kicsit bekeményítenénk. Amennyiben nagyon furdal a kíváncsiság, egy rövid ismertetővel azért már most is szolgálhatunk. A main() függvény argumentumában levő dolgok ahhoz kellenek, hogy a programot Terminal-ból is lehessen futtatni. A programnak futásakor memóriára van szüksége. Amikor a futás befejeződött, más programok szeretnék használni - esetleg ugyanazt - a memória területet. A programozó feladata, hogy lefoglalja a szükséges memóriamennyiséget a programja számára és ugyanolyan fontos, hogy ezt fel is szabadítsa, amikor a programnak már nincsen rá szüksége. Erre szolgálnak a pool, drain szavakat tartalmazó utasítások. Fordít és futtat
Futtassuk le az Apple által megírt programot mielőtt bármit is tennénk vele [1]. Ne felejtsük - 31 -
el kinyitni a Console ablakot (a Run menüből), hogy láthassuk a futás eredményét. Ezután kattintsunk a kalapácsos ikonra (Build and Go) annak érdekében, hogy megtörténjen a fordítás és a futtatás.
Fordít és futtat gomb. A program ekkor lefut és a futás eredménye látható lesz a Console ablakban néhány további információval együtt. Az utolsó sorban levő mondat arról szól, hogy a 0 visszatérési kóddal állt le. Jól látható hogy megkaptuk a main() függvény zéró visszatérési értékét, ahogy arról a 3-as fejezetben már beszéltünk [10.9]. Ez ugye azt jelenti, hogy a programunk nem állt le idő előtt, hanem sikeresen végrehajtott minden szükséges utasítást. Ez igazán jó hír! Hibakeresés
Térjünk vissza az [1]-es programkódhoz és vizsgáljuk meg mi történik, ha hiba kerül a kódba. Például kicseréljük az NSLog() utasítást egy másikra, de elfelejtjük a pontosvesszőt az utasítás végéről. //[2] #import //[2.2] int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // ide kerül a programkód ... NSLog(@"Step1: create a bug!") // [2.8] Hoppá, elfelejtettük a pontosvesszőt! [pool drain]; //[2.9] return 0; }
Kattintsunk a Build ikonra az eszköztárban. Nyomban megjelenik egy piros kör a [2.8] utasítás előtt.
- 32 -
Az Xcode hibaüzenetei Ha rákattintasz, akkor az eszköztár alatt egy rövid magyarázat jelenik meg: error: szintaktikai hiba a "drain" előtt
A szintaktikai, formai elemzés az első feladata a fordítóprogramnak: lépésenként ellenőrzi a program minden sorát, hogy formailag helyes-e. Annak érdekében, hogy segítsd a munkáját bizonyos szabványos jeleket kell elhelyezned a programban. Ilyen például az, hogy az import utasítást [2.2] a # jellel kezd kezdeni. Az utasítás végét [2.8] pedig a pontosvesszővel kell jelezned. Amint a fordítóprogram a [2.9]-es sorhoz érkezik, észreveszi, hogy valami rossz. Azt sajnos nem érzékeli pontosan, hogy nem ebben a sorban van a hiba, hanem az előző sorban, ahol ugye hiányzik a pontosvessző. Tehát a legfontosabb tanulság ezen a ponton az, hogy a fordítóprogram megkíséreli ugyan, hogy a lehető legpontosabb visszajelzést adjon, de egyáltalán nem biztos, hogy pontos leírását kapjuk a hibának, sőt még az sem biztos, hogy a konkrét hiba pontos előfordulási helyén jelzi a problémát, csak próbál közelíteni ahhoz. Javítsd ki a hibát, azaz add hozzá a pontosvesszőt a megfelelő helyre és futtasd ismét, hogy meggyőződjél, most már jól működik. Az első progarmunk
Most vegyük elő az előző fejezetben megírt programkódot és illesszük be az utasításokat az Apple által szolgáltatott programváz [1] megfelelő helyére, ahogy azt a [3]-as példában láthatjuk. //[3] #import float circleArea(float theRadius); // [3.3] float rectangleArea(float width, float height); // [3.4] int main (int argc, const char * argv[]) // [3.6] { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int pictureWidth;
- 33 -
float pictureHeight, pictureSurfaceArea, circleRadius, circleSurfaceArea; pictureWidth = 8; pictureHeight = 4.5; circleRadius = 5.0; pictureSurfaceArea = pictureWidth * pictureHeight; circleSurfaceArea = circleArea(circleRadius); NSLog(@"A kép területe: %f. A kör területe: %10.2f.", pictureSurfaceArea, circleSurfaceArea); [pool drain]; return 0; } float circleArea(float theRadius) // [3.22] { float theArea; theArea = 3.1416 * theRadius * theRadius; return theArea; } float rectangleArea(float width, float height) // [3.29] { return width*height; }
Ne sajnáld az időt arra, hogy megértsd a program szerkezetét! A circleArea() [3.22] és rectangleArea() [3.29] függvények fejléce [3.3, 3.4] a main() függvény [3.6] előtt láthatók, ahol lenniük is kell. A saját függvényeink a main() függvény kapcsos zárójelein kívül helyezkednek el. A programkódunkat a main() függvény azon részére illesztettük be, ahova az Apple kérte. Ha lefuttatjuk a programot, akkor a következő eredményt kapjuk: A kép területe: 36.000000. A kör területe: justatry has exited with status 0.
78.54.
Hibakeresés
Minél összetettebb lesz a programkód, annál nehezebbé válik a hibakeresés. Időnként hasznos lehet megtudni, hogy mi történik a program belsejében, miközben az fut. Az Xcode erre egy nagyon egyszerű lehetőséget biztosít. Válaszd ki azt a változót és azt a sort a programban ahol kíváncsi vagy a változó értékére majd kattints az utasítás melletti szürke margóra. Az Xcode egy töréspontot (breakpoint) helyez a programkódba és ezt egy kék szürke nyíllal fogja jelezni.
- 34 -
Töréspont elhelyezése a programkódba Jegyezd meg, hogy az adott utasítás végrehajtása előtti változó értéket kapjuk meg ezen az úton, ezért gyakran szükség lehet, hogy a töréspontot egy lépéssel későbbre helyezzük el. Most mozgasd az egeret a második kalapácsos gomb főlé az eszköztárban, kattints egyet és egy pop-up menüt fogsz kapni.
A Build and Go (Debug) popup menü Válaszd ki: Build and Go (Debug). Amennyiben ki van nyitva a Console és a Debugger ablak (a Run menüből), akkor a Console ablakban ilyesmit fogunk látni: [Session started at 2009-06-03 15:48:02 +1000.] Loading program into debugger… GNU gdb 6.3.50-20050815 (Apple version gdb-956) (Wed Apr 30 05:08:47 UTC 2008) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions.
- 35 -
Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-apple-darwin".tty /dev/ttys000 Program loaded. sharedlibrary apply-load-rules all run [Switching to process 86746 local thread 0x2d03] Running…
Ez mutatja, hogy a program fordítása megtörtént, betöltődött és a hibakereső is elindult. A Debugger ablakban pedig ezt láthatjuk:
Az Xcode hibakeresője lehetővé teszi számodra, hogy lépésenként futtasd a programot és közben figyeld a változók állapotát A program most addig fut, amíg eléri az első töréspontot. A Debugger jobb felső ablakában láthatod a különböző változók aktuális állapotát. Az utoljára beállított, vagy megváltoztatott - 36 -
értéket mindig piros karakterrel jelzi. A Continue gombra kattintva folytathatjuk a program futását. A hibakereső egy nagyon hasznos dolog az Xcode-ban. Szánjál rá időt, hogy megismerd és megszokd a használatát. Befejezés
Mindent láttunk most már: hogy kell írni, tesztelni és futtatni egy egyszerű programot a Mac OS X alatt. Amennyiben megelégszel olyan programokkal, amelyek nem használnak grafikus felhasználói felületet, akkor már nincs más dolgod, mint tökéletesíteni az Objective C-vel kapcsolatos tudásodat és egyre tökéletesebb nem grafikus programokat fogsz tudni írni. A következő fejezetekben éppen ehhez fogunk segítséget adni, és azután pedig következhetnek a GUI alapú programok. További jó olvasást!
- 37 -
06: Feltételes elágazások BecomeAnXcoder - 06: Conditional Statements
Feltételes elágazások
if() Van amikor csak akkor szeretnéd, hogy utasítások egy sorozata végrehajtódjon, ha bizonyos feltételek teljesülnek. Speciális utasítások segítségével ezt elérhetjük [1.2]. //[1] az age egy egész típusú változó, a felhasználó életkorát tartalmazza if (age > 30) // [1.2] A > szimbólum azt jelenti, hogy "nagyobb, mint" { NSLog(@"age öregebb, mint harminc."); //[1.4] } NSLog(@"Vége."); //[1.6]
Az [1.2] sor tartalmazza az if() utasítást, amit feltételes utasításnak szoktak nevezni. A zárójelek tartalmazzák azt a kódot, aminek a segítségével el kell dönteni, hogy zárójelek közötti logikai kifejezés értéke igaz-e. Jelen példában, ha az age > 30 feltétel teljesül, akkor az [1.4] string ki lesz nyomtatva. A feltétel akár teljesül, akár nem, az [1.6] string mindenképpen ki lesz nyomtatva, mivel ez nincs az if() utasítás zárójelei között. if() else() Az if...else utasítás segítségével egy alternatív utasítás sorozatot is lefuttathatunk, amikor a feltétel nem teljesül [2]. //[2] az age egy egész típusú változó, a felhasználó életkorát tartalmazza if (age > 30) //[2.2] { NSLog(@"age öregebb, mint harminc."); //[2.4] } else { NSLog(@"age nem öregebb, mint harminc."); //[2.7] } NSLog(@"Vége.");
- 38 -
A [2.7] sorban levő string csak akkor lesz kinyomtatva, ha a feltétel nem teljesül [2]. Összehasonlítás A nagyobb jel mellett [2.2] a következő összehasonlító operátorok állnak még a rendelkezésedre. == > < >= <= !=
egyenlő nagyobb kisebb nagyobb egyenlő kisebb egyenlő nem egyenlő
Külön szeretném felhívni a figyelmet az egyenlő operátorra. Ez két darab egyenlőség jelet tartalmaz! Ezt bizony könnyen el lehet téveszteni, és helyette csak egy egyenlőség jelet írni. Az viszont az értékadás operátora, tehát összehasonlítás helyett az adott értéket adja a változónak. Ez egy tipikus hibalehetőség kezdő programozóknál, érdemes rá odafigyelni. Mondd ki jó hagosan: Nem fogom elfelejteni, hogy dupla egyenlőség jelet írjak, amikor az egyenlőséget akarom tesztelni! Az összehasonlító operátorok meglehetősen hasznosak amikor meghatározott utasítás sorozatot többször is szeretnénk egymás után végrehajtani. Ez lesz a következő fejezet témája. De előtte vizsgáljuk meg a feltételes utasítást néhány más aspektusból. Gyakorlat Vizsgáljuk meg közelebbről az összehasonlítást. Az összehasonlítás eredménye valamelyik a két lehetséges eset közül: az eredmény vagy igaz, vagy hamis. Az Objective-C nyelvben az igaz és a hamis értéknek rendre az 1 illetve a 0 érték felel meg. Van egy speciális adattípus: BOOL, amelyik segítségével ábrázolni tudod ezeket az értékeket. Az igaz értéket az 1, vagy a YES is kifejezheti, a hamisat pedig a 0, vagy a NO. //[3] int x = 3; BOOL y; y = (x == 4);
// y értéke 0 lesz.
Egyszerre több feltételt is vizsgálhatunk. Ha egyszerre két feltételnek kell teljesülnie, akkor használd a logikai AND-et, amit két és jellel is jelölhetünk: &&. Ha egyszerre csak az egyik feltételnek kell teljesülnie, akkor használd a logikai OR-t, amit két függőleges vonallal is jelölhetünk: ||. //[4] if ( (age >= 18) && (age < 65) )
- 39 -
{ }
NSLog(@"Valószínűleg dolgoznia kell a megélhetésért.");
A feltételes utasításokat egymásba is ágyazhatjuk. Egyszerűen az egyik feltételes utasítást a másik feltételes utasítás kapcsos zárójelei közé helyezzük. Először a külső feltételes utasítás lesz végrehajtva, és ha a feltétel teljesül, akkor a következő utasítás a belsejében, és így tovább ...: //[5] if (age >= 18) { if (age < 65) { NSLog(@"Valószínűleg dolgoznia kell a megélhetésért."); } }
- 40 -
07: Ciklusok BecomeAnXcoder -07: Repeating Statements for a While
Ciklusok
Beveyető Minden eddigi programkódunkban az utasítások csak egyszer hajtódtak végre. Úgy is lehet ismételtetni függvényekben levő programrészeket, hogy többször meghívjuk őket [1]. //[1] NSLog(@"Julia a kedvenc színésznőm."); NSLog(@"Julia a kedvenc színésznőm."); NSLog(@"Julia a kedvenc színésznőm.");
Itt a függvényhívásokat ismételgettük. Gyakran előfordul, hogy egy, vagy több utasítást többször is ismételni kell. Az Objective C a többi programozási nyelvhez hasonlóan többfajta lehetőséget is biztosít erre. for() Ha pontosan tudod a szükséges ismétlések számát, akkor ezt a számot beírhatod a for ciklus fejrészébe ahogy a [2]-es példában látható. Ez a szám csak egész szám lehet, hiszen nem lehet megismételni egy műveletet mondjuk 2.7-szer. //[2] int x; for (x = 1; x <= 10; x++) { NSLog(@"Julia a kedvenc színésznőm."); } NSLog(@"Az x értéke %d", x); //[2.6]
//[2.4]
A [2]-es példában a [2.4] string 10-szer lesz kinyomtatva. Első lépésben az x felveszi az 1 értéket. Ezután a számítógép elvégzi a vizsgálatot: x <= 10. Ez a feltétel teljesül (mivel x egyenlő 1-gyel), ezért a kapcsos zárójelen belül levő utasítások végrehajtódnak. Ezután az x értékét megnöveli a program - itt most 1-gyel - az x++ kifejezés hatására. Az eredmény, ami most 2 lesz, ismét összehasonlításra kerül a 10-zel. Mivel az még mindig kisebb mint 10, a kapcsos zárójeleken belüli utasítások ismét végrehajtódnak. Amint x egyenlő lesz 11-gyel, az x <= 10 feltétel már nem fog teljesülni. Az utolsó utasítás [2.6] pontosan megmutatja, hogy x értéke 11 és nem 10, miután a ciklus befejeződött. Amennyiben nagyobb lépésekre van szükség a ciklusváltozó növelésekor, akkor az x++ - 41 -
helyett a megfelelő értéknövelő kifejezést kell írni. A következő példa a Fahrenheit fokokat váltja át Celsius fokra 20-as lépésekben. //[3] float celsius, tempInFahrenheit; for (tempInFahrenheit = 0; tempInFahrenheit <= 200; tempInFahrenheit = tempInFahrenheit + 20) { celsius = (tempInFahrenheit - 32.0) * 5.0 / 9.0; NSLog(@"%10.2f -> %10.2f", tempInFahrenheit, celsius); }
A program futásának eredménye: 0.00 -> -17.78 20.00 -> -6.67 40.00 -> 4.44 60.00 -> 15.56 80.00 -> 26.67 100.00 -> 37.78 120.00 -> 48.89 140.00 -> 60.00 160.00 -> 71.11 180.00 -> 82.22 200.00 -> 93.33
while() Az Objective-C két másik lehetőséget is biztosít utasítások ismétlésére: while () { }
és do {} while ()
Formailag hasonlóan működnek, mint amit a for-ciklusnál már láttunk. Az első most is egy feltételvizsgálattal kezdődik. Ha az nem igaz, akkor a ciklusban levő műveletek nem hajtódnak végre. //[4] int counter = 1; while (counter <= 10) { NSLog(@"Julia a kedvenc színésznőm.\n"); counter = counter + 1; } NSLog(@"A számláló értéke %d", counter);
A számláló értéke s végén 11. A későbbiekben szükséged lehet erre a programban! A do {} while () utasításban a kapcsos zárójelek közötti rész legalább egyszer biztosan végrehajtódik, mert a vizsgálat csak azután történik.
- 42 -
//[5] int counter = 1; do { NSLog(@"Julia a kedvenc színésznőm.\n"); counter = counter + 1; } while (counter <= 10); NSLog(@"A számláló értéke %d", counter);
A számláló értéke a ciklus végén: 11. Most már egyre többet tudsz a programozásról, komolyabb feladatokra is képes leszel. A következő fejezetben elkészítjük első grafikus felhasználói felületet (GUI) használó programunkat.
- 43 -
08: Grafikus interfészt használó program BecomeAnXcoder - 08: A Program With a GUI
Grafikus interfészt használó program
Bevezető Most már elegendő Objective C ismeretünk van ahhoz, hogy elkezdjük a grafikus felhasználói felülettel (GUI) rendelkező programok írását. Be kell vallanom most valamit. Az Objectve C valójában a C nevű programozási nyelv kiterjesztése. Amiről idáig beszéltünk, az szinte minden a C nyelvről szólt. Tehát akkor miben is különbözik egymástól a két nyelv? A különbség az "Objective" részben keresendő. Az Objective C az absztrakt objektumok fogalmával operál. Idáig inkább csak számokkal dolgoztunk. Ahogy megtanultuk, az Objective C alapvetően támogatja a szám fogalmát. Ez azt jelenti, hogy lehetőség van számokat létrehozni a memóriában és matematikai műveletekkel, függvényekkel operálhatunk rajtuk. Ez nagyszerű, amikor a programod számokkal dolgozik (mint például egy számológép esetében), de mi a helyzet ha zeneszámokat tároló programot kell fejleszteni, ami zeneszámokkal, címlistákkal, művészekkel kell, hogy operáljon? Vagy mi a helyzet, ha egy repülésirányítási rendszer fejlesztése a cél, ahol repülőgépek, járatok, repülőterek az alapvető objektumok? Vajon lehetséges lenne, hogy az Objective C-ben ezekkel a dolgokkal ugyanolyan egyszerűen bánjunk, mint a számokkal? Éppen ez a dolog lényege. Az Objective C segítségével olyan típusú objektumokat tudsz definiálni, amilyenekre szükséged van és olyan alkalmazásokat tudsz írni, amelyek képesek ezekkel az objektumokkal dolgozni. Objektumok akció közben Példaképpen nézzük meg, hogyan is kezelünk egy ablakot egy Objective C-ben megírt program esetében. Ilyen például a Safari. Vessünk egy pillantást egy nyitott Safari ablakra a Mac-en. A bal felső sarokban van három gomb. A piros az ablak bezáró gomb. Mi történik, amikor bezárunk egy Safari ablakot úgy, hogy rákattintunk erre a kis piros gombra? Egy üzenetet küldünk az ablaknak. Válaszképpen erre az üzenetre az ablak lefuttat egy kis programkódot annak érdekében, hogy bezárja magát.
- 44 -
A bezár üzenet el lett küldve az ablaknak Az ablak egy objektum. Körül tudod rajzolni. A három gomb szintén egy-egy objektum. Rájuk tudsz klikkelni. Ezeknek az objektumoknak van vizuális megfeleltetése a képernyőn, de nem feltétlenül igaz ez minden objektumra. Például annak az objektumnak, amelyik kapcsolatot létesít a Safari és egy adott web oldal között, nincs vizuális megvalósítása a képernyőn.
Egy objektum (például az ablak) tartalmazhat más objektumokat (mint például a gombok)
Osztályok Annyi Safari ablakot tudsz megnyitni amennyit csak akarsz. Mit gondolsz, az Apple programozói: (a) leprogramozták az összes ablakot előre, kihasználva messzeföldről híres képzelőerőjüket, hogy neked hány darab ablakra lesz majd szükséged, vagy (b) készítettek egy mintát és megkérték a Safarit, hogy menet közben hozza létre neked ezeket az ablakokat?
Természetesen a helyes válasz a (b). Készítettek egy speciális kódot, amit osztálynak nevezünk és ez leírja, hogy mi az az ablak, hogyan néz ki és hogyan működik. Amikor készítesz egy új ablakot, akkor valójában ez az osztály készíti el az új ablakot. Ez az osztály tartalmazza az ablak leírását és mindegyik konkrét ablak ennek az osztálynak egy példánya (ugyanúgy, ahogy a 76 egy példánya a számok osztályának). - 45 -
Változó példányok Az általad megnyitott ablaknak van egy aktuális meghatározott helye a Mac képernyőjén. Amennyiben lekicsinyíted és a Dock-ba rakod, majd visszanyitod, akkor pontosan ugyanazt a pozíciót fogja a elfoglalni a képernyőn, mint korábban. Hogyan lehetséges ez? Az osztály definiál arra alkalmas változókat, amelyek megjegyzik az ablak képernyőn elfoglalt pozícióját. Az osztály példánya, azaz az objektum tartalmazza ezen változók aktuális értékét. Tehát minden ablak objektum tartalmaz bizonyos változó értékeket és különböző ablakok esetében általában más- és más értéket vesznek fel ugyanazok a változók. Metódusok Az osztály nem csak az ablak objektumot készítette el, hanem lehetőséget biztosít számára különböző akciók végrehajtására is. Az egyik ilyen művelet a bezárás. Amikor az ablak "Close" gombjára kattintasz, a gomb küld egy bezárás üzenetet az ablak objektuktumnak. Az objektum által elvégezhető műveleteket metódusoknak nevezzük. Ahogy láthatod, itt sok minden nagyon hasonlít a függvényekről tanultakra, tehát nem lesz nehéz dolgod a tanulásban feltéve, hogy eddig pontosan követtél minket. Objektumok a memóriában Amikor az ablak készít egy ablak objektumot számodra, akkor lefoglal részére egy memória (RAM) területet ahol tárolja az ablak pozícióját és más információkat. Azonban nem készít minden esetben egy újabb kódsorozatot az ablak bezárásának műveletére. Ez pazarlás lenne a számítógép memóriájával, mivel ez a kód minden egyes esetben ugyanaz. Az ablak bezárásának utasítássorozatát elegendő egyszer tárolni és lehetővé kell tenni, hogy az ablak osztályának minden ablak objektuma hozzáférhessen ahhoz. Mint korábban is, ebben a fejezetben is lesznek olyan kódrészletek, amelyek a memória foglalással és felszabadítással foglalkoznak, de ahogy korábban is említettük, ezekkel mélyebben most még nem foglalkozunk, csak sokkal később. Bocsánat. Gyakorlat
Az alkalmazásunk Készítünk egy kis alkalmazást két gombbal és egy szövegmezővel. Ha megnyomod az egyik gombot, akkor megjelenik egy szám a szövegmezőben. Ha megnyomod a másik gombot, akkor egy másik szám jelenik meg a szövegmezőben. Elképzelhetjük úgy is mint egy két gombos számológép, amelyik számításokat nem tud végezni. Természetesen amikor majd többet fogsz tudni, azt is kitalálod majd, hogyan lehet valóságos számításokat végeztetni, de jobban szeretem a kis lépésekben való haladást.
- 46 -
Az eltervezett alkalmazás vázlata Amikor az egyik gombot megnyomjuk, az küld egy üzenetet. Ez az üzenet tartalmazza annak a metódusnak a nevét, amit végre kell hajtani. Ez az üzenet el lesz küldve, de hova is? Az ablak esetében a bezárás üzenet annak az ablak objektumnak volt elküldve, amelyik az ablak osztály megfelelő példánya. Most tehát egy olyan objektumra lesz szükségünk, amelyik képes üzeneteket fogadni mindkét gombtól, és meg tudja mondani a szövegmező objektumnak, hogy jelenítsen meg egy adott értéket. Az első osztályunk Nos, mindenekelőtt el kell készítenünk egy osztályt, majd annak egy példányát. Ez az objektum fogja fogadni a gombok által küldött üzeneteket. (Kérlek ellenőrizd a lenti vázlatot.) Ugyanúgy, mint egy ablak objektum ez a példány is egy objektum, de az ablak objektummal szemben most nem látjuk a példányunkat a képernyőn futás közben. Ez csak a Macintosh memóriájának egy szelete lesz. Amikor a példányunk megkapja a két gomb közül valamelyik által küldött üzenetet, a megfelelő metódus lefut. Ennek a metódusnak a programkódja az osztályban található (nem a példányban magában). A végrehajtás során ez a metódus reszeteli (alapállapotba állítja) a szöveget a szövegmező objektumban. Honnan tudja a metódus az osztályunkban hogyan kell reszetelni a szöveget a szövegmezőben? Jelenleg ezt sehonnan nem tudja. De a szövegmező tudja, hogyan lehet reszetelni a saját szöveg tartalmát. Tehát egyszerűen küldünk egy üzenetet a szövegmező objektumnak, és megkérjük, hogy tegye meg ezt nekünk. Milyen típusú üzenetnek kell ennek lennie? Természetesen pontosan meg kell neveznünk a címzettet (azaz a szövegmező objektumot az ablakunkban). Ezen kívül meg kell mondani az üzenetben, hogy mit kérünk a címzettől. Ezt úgy tesszük, hogy megmondjuk annak a metódusnak a nevét, amit a szövegmezőnek futtatni kell amikor értelmezi az üzenetet. (Természetesen tudnunk kell milyen metódusokat tud futtatni a szövegmező és hogyan hívják őket.) Azt is közölnünk kell a szövegmező objektummal, hogy milyen értéket kell megjeleníteni (annak függvényében, hogy melyik gombot nyomtuk meg). Tehát az üzenet által közvetített kifejezés nem csupán a objektum és metódus nevét tartalmazza, hanem van egy argumentuma is, azaz egy paramétert is közvetít, amit a szövegmező objektum metódusának kell majd használnia. - 47 -
Az alkalmazásunk objektumai közötti üzenetváltások vázlata Most megmutatjuk hogyan küldünk üzenetet az Objective C-ben paraméter nélkül [1.1] és paraméterrel[1.2]: //[1] [receiver message]; //[1.1] [receiver messageWithArgument:theArgument];
//[1.2]
Mindkét utasításra jellemző, hogy szögletes zárójelek foglalják egységbe és a már jól ismert pontosvessző zárja le őket. A zárójelek között először a cél objektumot kell megnevezni, és ezt követi az objektum egyik metódusa. Amennyiben a meghívott metódus kötelezően egy, vagy több értéket is vár, akkor azt is meg kell adni [1.2]. Projekt készítés Most nézzük meg hogyan is működik ez a valóságban. Indítsuk el az Xcode programot, hogy készíthessünk egy új projektet. Válasszuk ki a Cocoa Application-t a Mac OS X Application menüből. Adjunk egy nevet, amit GUI alkalmazások esetében nagy betűvel szoktunk kezdeni. Az Xcode Groups & Files ablakában nyissuk ki a Resources (források) foldert. Kattintsunk kétszer a MainMenu.xib fájlra.
- 48 -
Kattintsunk kétszer a MainMenu.xib fájlra az Xcode-ban Az Xcode 3 előtt a xib fájlok végződése nib volt, ami a NeXT Interface Builder rövidítéséből származott. Ezért ha az Xcode-nak egy korábbi verzióját használod, akkor ott esetleg a MainMenu.nib fájlt kell megnyitnod. Ez egy lényegtelen különbség, minden más pontosan ugyanúgy működik. GUI készítés Ekkor elindul egy másik program, az Interface Builder (IB). Mivel több ablak is megnyílik, szükség esetén használhatod a Hide Other (elrejti a többit) opciót a File menüben. Három ablakot fogsz látni. Az egyik neve a "Window", ez lesz az az ablak, amit az alkalmazásod használója látni fog. Mellette ott látható a Library ablak. Ez az összes olyan objektum tárháza, amelyeket használhatsz a grafikus interfészedben, ezt könyvtár palettának is szokás nevezni. Válaszd ki a "Views & Cells"-t ennek az ablaknak a felső listájából és húzzál át két gombot egyesével a GUI ablakba: "Window". Hasonlóan húzzál egy szöveg mezőt (text Label) is a GUI ablakba.
- 49 -
GUI objectumok áthúzása a paletta ablakból az alkalmazás ablakba Azzal, hogy áthúzunk egy gombot a paletta ablakból az alkalmazás ablakba háttérben készül egy új gomb objektum és az bekerül az ablakodba. Ugyanez történik a szövegmezővel és minden más objektummal, amit a palettáról áthúzunk az ablakba. Megjegyezzük, hogy ha a paletta ablak egy ikonja felett tartod a kurzort, akkor egy név fog megjelenni, mint például NSButton, vagy NSTextView. Ez az Apple által szolgáltatott osztályoknak a neve. Hamarosan meg fogjuk mutatni, hogyan tudjuk megtalálni azokat a metódusokat ezekben az osztályokban, amelyeket használni akarunk a programunkban. Ne felejtsd el rendszeresen menteni a xib (nib) fájlokat, mert az Xcode és az IB így szinkronban marad. Rendezd el, méretezd át a "Window" ablakba húzott objektumokat ízlésednek megfelelően. Változtasd meg a gomb objektumokon levő szövegeket, ehhez kétszer rájuk kell kattintanod. Javaslom, hogy fedezd fel a paletta ablak tartalmát miután befejeztük ezt a projektet, hogy - 50 -
legyen egy elképzelésed arról, hogy a későbbiekben milyen objektumokat hogyan használhatsz még. Ismerkedés az Interface Builder-rel (IB) Ha kiválasztasz egy objektumot és megnyomod a Cmd-Shift-I billentyűkombinációt, akkor meg tudod változtatni a beállításokat. Gyakorold be ezt is. Például válaszd ki a "Window" ablakot (amit megtehetsz úgy is, hogy a xib ablakban rákattintasz egyszer) és nyomd meg a Cmd-Shift-I billentyűkonbinációt. Az Inspector tetején válaszd ki a Window Attributes-t. Itt jelöld be a Textured opciót és az ablakod máris fémes árnyalatú lesz. AZ IB erőssége abban rejlik, hogy az alkalmazásod megjelenítését nagyon sokféleképpen megváltoztathatod vele, ráadásul mindezt egyetlen programsor megírása nélkül!
Az ablakunk az Interface Builder-ben, valamint a hozzá tartozó Inspector ablak
Osztály háttér Ahogy megígértük korábban, létre fogunk hozni egy osztályt. De előtte még beszéljünk arról, hogyan működik egy osztály. - 51 -
Nagyon sok programozási munkát meg lehet spórolni azáltal, ha lehetőségünk van arra, hogy mások által már elkészített dolgokat beépítsünk ahelyett, hogy mindent nekünk kellene megírni a legapróbb részletektől kezdve. Ha például szeretnél készíteni egy speciális tulajdonságokkal (képességekkel) rendelkező ablakot, csak az egyedi tulajdonságoknak megfelelő részeket kell hozzáadnod. Nem kell ismételten megírni a programkódot az összes viselkedésre, mint például az ablak minimalizálása, vagy az ablak bezárása. Ingyenesen beépítheted, örökölheted ezeket a más programozók által már megírt a tulajdonságokat. Pontosan ez az amiben az Objective C lényegesen eltér a sima C programozási nyelvtől. Hogyan is működik ez valójában? Nos, itt van egy ablak osztály: NSWindow, te pedig tudsz írni egy olyan osztályt amelyik örökli ennek a tulajdonságait. Nézzük meg mi történik, ha ehhez hozzáadsz néhány saját tulajdonságot. Mi történik, ha a te ablakod kap egy "close", azaz egy bezárás üzenete? Nem írtál semmi ilyen műveletet, és nem is másoltál át olyan kódokat, amelyek ezt végre tudnák hajtani. Ilyenkor, azaz amikor az adott ablak osztálya nem tartalmazza az adott metódust, az üzenet automatikusan továbbítódik ahhoz az osztályhoz, ahonnan a mi ablak osztályunk öröklődik, tehát az ő szuperosztályához (superclass). És ha szükséges, akkor továbbmegy odáig, amíg a metódust megtalálja (vagy eléri az öröklődési hierarchia csúcsát.) Amennyiben a metódus nem található, akkor egy olyan üzenetet küldtél, ami nem végrehajtható. Ez olyan mintha megkérnénk egy autószervizt, hogy cseréljék ki a szánkónkon a kerekeket. Még a szerelőműhely főnöke sem tud ebben segíteni. Hasonló esetben az Objective C küld egy hibaüzenetet.
Saját osztályok Mi történik, ha szeretnél saját tulajdonságokkal felruházni a szuperosztályból származó metódusokat? Erre is van lehetőség, felül lehet írni a metódusokat. Például írhatsz egy olyan programkódot, amelyik a bezárás gombra kattintva csak láthatatlanná teszi, ahelyett hogy véglegesen bezárná azt. A speciális ablak osztályod ugyanazt a metódus nevet kell, hogy használja, mint az Apple által definiálté. Ezért amikor a te speciális ablakod kap egy bezárás üzenetet, akkor a te metódusod fog lefutni és nem az Apple-é, ezért az ablak csak látókörön kívül kerül mielőtt teljesen bezáródna. Figyelem, a végleges bezárás metódust az Apple leprogramozta. A mi saját bezárás metódusunk belsejéből még lehetséges meghívni az eredeti szuperosztályban levő metódust, mindamellett ezt egészen más formában kell megtenni annak érdekében, hogy a saját bezárás metódusunk ne kerüljön egy rekurzív ciklusba. //[2] // Ide kerül az ablakot láthatatlanná tevő kód. [super close]; // Használjuk a szuperosztály bezárás metódusát.
Ez a téma kicsit túlmutat jelen könyvünk keretein ezért nem várhatjuk el, hogy maradéktalan mindent megérts belőle.
- 52 -
Egy univerzális osztály Az NSObject osztály a király az összes osztály között. Szinte az összes osztály amit írni, vagy használni fogsz, direkt, vagy közvetett módon az NSObject osztály alosztálya lesz. Például az NSWindow osztály az alosztálya az NSResponder osztálynak, amelyik az alosztálya az NSObject osztálynak. Az NSObject osztály közös metódusokat definiál az összes objektumnak (például az objektum szöveges leírását generálja, megkérdezi az objektumtól, hogy érti-e az adott üzenetet és így tovább.) Mielőtt további unalmas elmélettel untatnálak, nézzük hogyan kell elkészíteni egy osztályt. Az osztályunk elkészítése Vegyük elő az Xcode projektünket és válasszuk ki a New File-t a File menüből. Válasszunk ki egy Objective-C osztályt a listából, majd kattintsunk a Next gombra. Nevezzük el például "MAFoo"-nek, majd kattintsunk a Finish gombra.
A MAFoo osztály készítése
- 53 -
A MAFoo név első két karaktere a My Application rövidítése. Természetesen bármilyen nevet kitalálhatsz, de amikor a saját alkalmazásaidat írod, feltétlenül javasoljuk, hogy az osztályod neve ugyanazzal a 2-3 karakterrel kezdődjön, mert így legkönnyebb elkerülni létező osztálynevekkel való ütközést. De semmiképpen se kezd őket az NS karakterekkel. Az Apple osztályai kezdődnek így. Ez a NeXTStep névből származik. A NeXTStep-ről érdemes tudni, hogy ez volt a Next cég által fejlesztett és használt operációs rendszer neve, amit az Apple megvásárolt és ez lett a Mac OS X alapja, valamint ráadásként Steve Jobs is visszakerült az Apple-höz. A CocoaDev wiki tartalmaz még egy sor ajánlást, hogy mivel ne kezdjünk osztályneveket. Feltétlenül tanulmányozd, mielőtt a saját osztálynevedet megválasztod: http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix Az osztály nevét célszerű úgy megválasztani, hogy jellemző információkat tartalmazzon az osztályról magáról. Példaképpen vegyük a már ismert NSWindow Cocoa osztályt, amelyik az ablakot (window) reprezentálja. Egy másik példa a színeket reprezentáló osztály, aminek a neve NSColor. Esetünkben a MAFoo osztály azt mutatja be, hogyan tudnak az objektumok kommunikálni egymással egy alkalmazásban. Ezért adtunk neki egy általános nevet, mindenfajta speciális jelentés nélkül. Egy példány készítése az Interface Builder-ben Vegyük elő ismét az Interface Buildert és a library palettában a főmenüből válasszuk ki az Objects & Controllers-t. Ezután húzzunk egy objektumot (kék kocka) a palettáról a MainMenu.xib osztályra.
- 54 -
Egy új objektum példányosítása Ezután válasszuk ki az Identity gombot az Inspector palettáról (Cmd-6), majd válasszuk ki a MAFoo osztályt a legördülő menüből. Ezzel a xib fájlunk egy objektumába példányosítottuk a MAFoo osztályunkat az Xcode-ban. Ez lehetővé fogja tenni, hogy a programkódunk és az interfészünk kommunikálni tudjon egymással.
- 55 -
Az objektum példányunk azonosságának beállítása
Kapcsolatok létrehozása A következő lépésben kapcsolatokat létesítünk a gombok (ahonnan az üzenetek lesznek küldve) és a MAFoo objektum között. Ezen kívül létesítünk egy kapcsolatot a MAFoo objektumból vissza a szövegmezőhöz, mivel a szövegmező objektumnak is fogunk egy üzenetet küldeni. Egy objektum nem tud üzenetet küldeni egy másik objektumnak, amennyiben nem tartalmaz hivatkozást a másik objektumra. Amikor egy kapcsolatot létesítünk a gomb és a MAFoo osztály között, akkor ezt a gombot egy hivatkozással rendeljük hozzá a MAFoo objektumhoz. Ezt a hivatkozást használva, a gomb képes lesz arra, hogy üzenetet küldjön a MAFoo objektumunk számára. Hasonlóan, létrehozva egy kapcsolatot az objektumunkból a szövegmező objektumra, lehetővé teszi később az üzenetküldést. Gondoljuk át még egyszer milyen alkalmazást kell fejlesztenünk. Mindkét gomb, ha rákattintunk, képes egy üzenetet küldeni az adott akciónak megfelelően. Ez az üzenet tartalmazza a MAFoo osztály azon metódusának a nevét, amit végre kell hajtani. Az üzenet a MAFoo osztály éppen most létrehozott példányának, a Mafoo objektumnak lesz elküldve. (Emlékeztetnénk, hogy az objektum példány nem tartalmazza az akció végrehajtásához szükséges programkódot, de az osztály igen.) Ezért a MAFoo objektumnak elküldött üzenet elindítja a MAFoo osztály egy metódusát, ami elvégzi a feladatot: jelen esetben küld egy üzenetet a szövegmező objektumnak. Mint minden üzenet, ez is tartalmazza a metódus nevét (amit a szövegmező objektumnak végre kell hajtani). Ebben az esetben a szövegmező objektum metódusának az a feladata, hogy megjelenítsen egy értéket. Ezt az értéket az üzenet részeként kellett elküldeni (ezt hívtuk argumentumnak, ugye emlékszel?) a szövegmezőn meghívott metódus nevével együtt. - 56 -
Az osztályunknak szüksége van két műveletre (metódus), ami a két gomb objektum által lesz meghívva. Az osztályunknak szüksége lesz még egy outlet-re, egy változóra annak érdekében, hogy emlékezzen arra, melyik objektumnak lett elküldve az üzenet (az adott példában ez ugye a szövegmező objektum). Győződj meg róla, hogy a MAFoo ki van választva a MainMenu.xib ablakban. A billentyűzeten nyomd meg a Cmd-6 kombinációt annak érdekében, hogy az Identity Inspector ablak legyen aktív. Ebben az ablakban, az Action részben kattints az Add (+) gombra, hogy hozzáadjunk egy akciót (azaz egy akció metódust) a MAFoo osztályhoz. Az Interface Builder által felajánlott nevet cseréljük ki egy beszédesebbre (például beírhatjuk a "setTo5:"-öt, hiszen azt fogjuk beprogramozni, hogy jelenítse meg az 5-ös számot a szövegmezőben). Adjunk egy másik metódust is a MAFoo osztályhoz és ne feledkezzünk meg a névadásról sem (például legyen ez a "reset:", hiszen azt fogjuk beprogramozni, hogy jelenítse meg a 0 számot a szövegmezőben). Felhívjuk a figyelmet arra, hogy mindkét metódus név kettőspontra végződik. Erre majd még később visszatérünk. Az Inspector ablakban kicsit lejjebb, most válasszuk ki az Outlet részt, adjunk hozzá egy új outletet és nevezzük is el (például "textField").
A MAFoo osztályhoz akció és otlet metódusokat adunk hozzá Mielőtt létrehozzuk az objektumok közötti kapcsolatokat, adjunk beszédes nevet a két gombnak. Mivel az elsőt arra szánjuk, hogy kérje meg a MAFoo példányunkat az 5-ös szám megjelenítésére a szövegmezőben, ezért ennek adjuk a "Set to 5" nevet (már korábban láttuk, hogyan lehet a gomb nevét megváltoztatni: kétszer kattintsunk a nevére a képernyőn és írjuk - 57 -
be az új nevet). Hasonlóan nevezzük el a másikat "Reset"-nek. Megjegyezzük, hogy a gomboknak történő névadás nem szükséges a program korrekt működéséhez. Ennek csupán az a célja, hogy azt szeretnénk, ha a felhasználói felületünk a lehető legérthetőbb lenne a felhasználó számára. Most már valóban készen vagyunk arra, hogy létrehozzuk az aktuális kapcsolatokat a - a "Reset" gomb és a MAFoo példány - a "Set to 5" gomb és a MAFoo példány - a MAFoo példány és a szövegmező között. A kapcsolatok létrehozása a következőképpen történik. Nyomd le a Control (ctrl) billentyűt és az egérrel a "Set to 5" gombot kösd össze a MAfoo példánnyal a MainMenu.xib ablakban (ne visszafelé csináld!). A kapcsolatot jelképező vonal jól látható a képernyőn. Az objektum példány ikonjánál pedig egy legördülő menő lesz látható. Válaszd ki a "setTo5:"-t a listából.
A kapcsolat felállítása
- 58 -
Most már a gomb rendelkezik egy, a MAFoo objektumra mutató hivatkozással és el fogja küldeni neki a setTo5: üzenetet, amikor a gomb megnyomásra kerül. Ezután a "Reset" gombot is összeköthetjük a MAFoo objektummal pontosan ugyanilyen módon. Ahhoz, hogy létrehozzuk a kapcsolatot a MAFoo objektum és a szövegnező között, kezdjük a MAFoo objektumnál, nyomjuk le a Control billentyűt és kössük össze a szövegmező objektummal. Válasszuk ki a "textField"-et a menüből és ezzel kijelöltük a megfelelő kapcsolatot. Mi a csuda történt most tulajdonképpen? Nos, néhány percen belül látni fogod, hogy anélkül hoztál létre egy programkódot, hogy egyetlen utasítást is leírtál volna! Kódgenerálás Először is győződj meg róla, hogy a MAFoo ki van választva a MainMenu.xib ablakban. Az Interface Builder File menüjéből válasszuk ki a Write Class Files-t. Az Interface Builder ekkor megkérdezi, hogy hova szeretnéd a programkódot elmenteni. Válaszd ki az alkalmazásunk projekt folderét és írasd felül a már létező MAFoo osztályt. Ellenőrizd, hogy a Language legördülő menüben az Objective C van kiválasztva és jelöld be, hogy "Create '.h' file".
- 59 -
És most, ha visszamész az Xcode-hoz, megnézheted a legenerált fájlokat a projekt ablakon belül a Classes csoportban. Ha véletlenül a Resources, vagy másik csoportba kerültek volna, akkor egyszerűen fogd meg és húzd át a Classes csoportba őket. Kattints az Editor gombra az eszköztárban és válaszd ki a MAFoo.h fájlt.
- 60 -
A legenerált programkódok látszanak az Xcode projektben Emlékezzünk vissza a 3. fejezetre, amikor a függvényekről beszéltünk. Emlékszel a függvény fejlécére [11.1]? Ez egy figyelmeztetés volt a fordítóprogram számára, hogy mire kell számítania. Az imént két fájlt generáltunk, ezek közül az egyik a MAFoo.h, és ez egy fejléc (header): alapvető információkat tartalmaz az osztályunkról. Például a [3.5] sorban látható egy NSObject, és ez a sor azt mutatja, hogy az osztályunk az NSObject osztályból öröklődik. //[3] /* MAFoo */ #import // [3.2] @interface MAFoo : NSObject // [3.5] { IBOutlet id textField; // [3.7] } - (IBAction)reset:(id)sender; // [3.9] - (IBAction)setTo5:(id)sender; // [3.10] @end
Van itt egy outlet [3.7] is, ami a szövegmező objektumra mutat. Az id jelöli az objektumot. Az "IB" rövidítés pedig az Interface Builder-re utal, a programra, aminek segítségével ezt a kódot generáltuk. - 61 -
IBAction [3.9, 3.10] ugyanazt jelenti, mint a void. Semmit nem kap vissza az objektum,
amelyik küldi az üzenetet: a gombok ebben a programban nem kapnak semmilyen választ a MAFoo objektumtól az üzenetükre. Láthatsz itt két Interface Builder akciót. Ez az osztályunk két metódusa. A metódusok majdnem olyanok, mint a már jól ismert függvénye, de van azért egy kis különbség közöttük. Erről majd később még lesz szó. Korábbi alkalmazásainkban az #import utasítással találkoztunk a mostani [3.2] helyett. A korábbi a nem GUI alkalmazásokhoz kellett, ez pedig a grafikus interfészt használók számára szükséges. Most pedig vizsgáljuk meg a másdik generált fájlt, a MAFoo.m-t. Ismét örülhetünk, hiszen potyára kaptunk egy programkódot. //[4] #import "MAFoo.h" @implementation MAFoo - (IBAction)reset:(id)sender // [4.5] { } - (IBAction)setTo5:(id)sender { } @end
Mindenekelőtt vegyük észre, hogy a MAFoo.h header fájl be van importálva, tehát a fordítóprogram tudja, hogy mire számíthat. Két metódust láthatunk itt: reset: és setTo5:. Ezek az osztályunk metódusai. A függvényekhez hasonlóan itt is kapcsos zárójelek közé kell illeszteni a programkódot. Amikor megnyomjuk az egyik gombot, akkor az küld egy üzenetet a MAFoo objektumnak és megkéri az egyik metódus végrehajtását. Ennek a kódját az Interface Builder már elkészítette számunkra amikor összekötöttük a gombokat a MAFoo objektummal. Ezzel tehát nincs több dolgunk. Azonban életre kell keltenünk a két metódust, azaz meg kell írni az általuk tartalmazott függvények kódját. Jelen esetben ezek a metódusok semmi mást nem csinálnak, mint üzenetet küldenek a MAFoo objektumból a textField objektumnak, amint beírjuk az [5.7, 5.12] utasításokat. //[5] #import "MAFoo.h" @implementation MAFoo - (IBAction)reset:(id)sender { [textField setIntValue:0]; // [5.7] } - (IBAction)setTo5:(id)sender { [textField setIntValue:5]; // [5.12] } @end
- 62 -
Ahogy látható, küldünk egy üzenetet annak az objektumnak, amire a textField outlet hivatkozik. Mivel az Interface Builder-ben összekötöttük ezt az outletet az aktuális szövegmezővel, az üzenetünk a korrekt objektumhoz kerül. Az üzenet nem más, mint a metódus neve setIntValue: és egy egész szám. A setIntValue: metódus alkalmas arra, hogy megjelenítsen egy egész számot egy szövegmező objektumban. A következő fejezetben azt is eláruljuk, hogyan találtunk rá erre a metódusra. Készen is vagyunk Most már készek vagyunk arra, hogy lefordítsuk és futtassuk az alkalmazásunkat. Kattintsunk a Build and Go gombra az Xcode eszköztárában. Egy kis időre van szüksége az Xcode-nak ahhoz, hogy lefordítsa és betöltse a programot. Végül is az alkalmazás megjelenik a képernyőn és elkezdheted tesztelni.
Az alkalmazásunk futás közben Röviden összefoglalva: készítettél egy (nagyon kezdetleges) alkalmazást, és ehhez neked csupán két programsort kellett írnod!
- 63 -
09: Keresési módszerek BecomeAnXcoder - 09: Finding Methods
Keresési módszerek
Bevezető Az előző fejezetben a metódusokról tanultunk. Két metódus törzsét mi írtuk és felhasználtunk egy Apple által szolgáltatottat is. A setIntValue: metódus arra szolgált, hogy egy egész számot megjelenítsünk egy szövegmező objektumban. Hol lehet megtalálni az általunk felhasználható metódusokat? Emlékeztetnénk arra, hogy az Apple által szolgáltatott metódusok esetében neked egyetlen kódot sem kell írnod. További előnyük, hogy hibamentesek. Ezért mindenképpen tanácsos körülnézni, hogy van-e megfelelő kész metódus, mielőtt belevágnánk a programozásba.
Gyakorlat Ha az Interface Builder-ben a paletta ablakban egy objektum fölé mozgatjuk a kurzort, vagy kiválasztjuk azt, akkor egy kis ablakban megjelenik egy rövid leírás. Amennyiben egy gombot választunk ki az egérrel, akkor azt fogjuk látni, hogy "NSButton". Amennyiben egy "System Font Text"-et tartalmazó szövegmezőt, akkor azt látjuk, hogy "NSTextField". Ezek mindegyike egy osztály neve. Nézzük meg most, hogy az NSTextField osztályban milyen metódusok érhetők el. Az Xcode-ban válasszuk ki a Help → Documentation menüt. A bal oldali oszlopban válasszuk ki a Cocoa (Core Library)-t, az API keresési módot a felső sorból és írjuk be az "NSTextField"-et a kereső mezőbe. (Lásd az ábrát.) Amint elkezded beírni a keresőkifejezést, a lehetséges találatok fokozatosan szűkülnek, míg végül az NSTextField fog látszódni a lista tetején. Kattints az NSTextField osztálytípusra, hogy az alatta levő mezőben megjelenjen annak részletes leírása.
- 64 -
Navigáció a Cocoa documentációban az Xcode segítségével Az első dolog, amit meg kell jegyezni az az, hogy ez az osztály sok mindent örököl más osztályoktól. Az utolsó, az (Inherits from) sorban a főkutya, a mindenekfelett álló NSObject. Az oldalon kicsit lejjebb található a következő cím (szkrollozni kell hozzá): Methods Itt fogjuk kezdeni a keresést. Ha átfutjuk az alcímeket, gyorsan látni fogjuk, hogy itt nem találjuk meg azt a metódust, aminek a segítségével ki tudunk íratni egy értéket a szövegmező objektumba. Az öröklődés elve alapján meg kell találni a sorban a következő szuperosztályt. Az NSTextField osztályt közvetlenül tartalmazó szuperosztály az NSControl (ha itt sem járnánk sikerrel, akkor jöhet a következő, az NSView osztály, és így tovább). Mivel az egész dokumentáció HTML alapon működik, nagyon egyszerű a navigáció, csak rá kell kattintani az NSControl linkre, ( az Inherits from listában látható). Ezzel megkapjuk az NSControl osztály információs oldalát: NSControl Inherits from NSView : NSResponder : NSObject
- 65 -
Látható, hogy egy osztállyal feljebb léptünk. A metódus listában észrevehetünk egy alcímet: Setting the control's value (Vezérlő elemek beállítása) Éppen ez az amit keresünk, hiszen egy értékbeállításra van szükségünk. Alatta találjuk a következő alcímet: - setIntValue: Biztatóan hangzik, ezért ennek a metódusnak elolvassuk a leírását rákattintva a setIntValue: linkre. setIntValue: - (void)setIntValue:(int)anInt Beállítja a fogadó (vagy a kiválasztott) cella értékét az anInt egész értékre. Ha a cella szerkesztés alatt áll, akkor megszakít minden szerkesztési folyamatot mielőtt beállítja az értéket; ha a cella nem az NSActionCell-ből öröklődik, akkor kijelöli a cellának a belsejét a megjelenítés frissítéséhez szükséges módon (az NSActionCell viszont a saját frissítési módszerét alkalmazza). Az alkalmazásunkban az NSTextField objektumunk a fogadó és ide kell egy egész értéket betöltenünk. Ezt a metódus kódjából is láthatjuk: - (void)setIntValue:(int)anInt
Az Objective C nyelvben a metódus példány kezdetét egy mínusz jel jelöli (az osztály metódus deklarációról majd később fogunk beszélni). A void azt jelöli, hogy nincs semmilyen visszatérési érték a metódusból a hívó fél felé. A konkrét alkalmazásunkban amikor küldünk egy setIntValue: üzenetet a textField objektumnak a MAFoo objektum nem kap vissza semmilyen értéket a szövegmező objektumtól. Ez rendben is van. A kettőspont után látható (int) azt mutatja, hogy az anInt változó értéke egész típusú kell, hogy legyen. A konkrét feladatunkban az 5 és a 0 értékeket küldjük vele, tehát nyugodtak lehetünk. Néha egy kicsit bonyolultabb megtalálni a legmegfelelőbb metódust. Jelentősen megkönnyíti a dolgodat, ha ismerős vagy a dokumentációban, javasoljuk tehát annak olvasgatását. Mi a helyzet, ha a textField szövegmező objektumból szeretnénk kiolvasni egy értéket? Emlékszel a függvények hasznos tulajdonságára, mely szerint a belső változók rejtettek? Ugyanez a helyzet a metódusokkal is. Az objektumok metódusai azonban gyakran párba állíthatók, amiket "Accessor"-oknak (kiegészítőknek) nevezünk, az egyik az érték olvasására, a másik az érték beállítására szolgál. Mi már ismerjük a másodikat, ami a setIntValue: metódus. Az első, ennek a párja pedig így néz ki: //[1] - (int) intValue
Amint az jól látható, ez a metódus egy egész értékkel tér vissza. Ezért ha a textfield - 66 -
objektumunkhoz kapcsolódóan egy egész értéket szeretnénk kiolvasni, akkor egy ilyen üzenetet kell küldeni: //[2] int resultReceived = [textField intValue];
Megismételjük: a függvényekben és a metódusokban is az összes változónév rejtett. Ez a változóneveknek egy kiváló tulajdonsága, hiszen nem kell aggódni, hogy a program egy részében beállított változó összegabalyodik egy ugyanolyan nevű másik változóval. Ugyanakkor a függvény neveknek egyedieknek kell lenniük. Az Objective-C egy lépéssel továbbmegy a védelemben: a metódusneveknek csak egy osztályon belül kell egyedinek lenniük, különböző osztályok viszont tartalmazhatnak ugyanolyan metódus neveket. Ez egy nagyon kellemes tulajdonság nagy programok esetén, hiszen a programozók egymástól függetlenül írhatják meg az osztályokat úgy, hogy nem kell aggódniuk metódusnév konfliktustól sem. Polimorfózisnak (görög eredetű szó) nevezzük azt a tényt, hogy különböző metódusok neve különböző osztályokban akár ugyanaz is lehet, és ez az egyik dolog, ami az objektumorientált programozást olyan különlegessé teszi. Ez lehetővé teszi, hogy úgy írjunk meg programrészeket, hogy fogalmunk sincs előre arról, hogy milyen osztályhoz tartoznak azok az objektumok, amelyekkel éppen dolgozunk. Csupán az szükséges, hogy futásidőben az aktuális objektumok megértsék a számukra küldött üzeneteket. A polimorfózisnak köszönhetően rugalmasan tervezhető és könnyen bővíthető programokat lehet írni. Például az általunk írt GUI alkalmazásban ha kicseréljük a szövegmezőt egy másik osztály olyan objektumára, amelyik megérti a setIntValue: üzenetet, az alkalmazásunk továbbra is működni fog anélkül, hogy módosítanánk a programkódot, vagy akár még újrafordítás nélkül is. Lehetőségünk van variálni az objektumokat futás közben úgy, hogy közben semmit sem kell megszakítani. Ebben rejlik az objektum-orientált programozás ereje.
- 67 -
10: awakeFromNib BecomeAnXcoder - 10: awakeFromNib
awakeFromNib
Bevezető Az Apple nagyon sokat dolgozott annak érdekében, hogy neked sokkal könnyebb legyen programokat írnod. A példa alkalmazásban sem kellett azzal törődni, hogyan rajzoljuk ki az ablakot és a gombokat a képernyőre sok egyéb más dolog közepette. A két legfontosabb framework (keretrendszer) amelyek tartalmazzák ezeket a dolgokat a Foundation Kit framework és az Application Kit. Az előbbi a 4. fejezet [12]-es példájában lett beimportálva és a nem grafikus felhasználói felületet használó programok legtöbb szolgáltatását tartalmazza. A második a képernyőn látható objektumokat és a felhasználó interakciós mechanizmusokat szolgáltatja. Mindkét keretrendszer igen jól dokumentált. Térjünk vissza a GUI alkalmazásunkhoz. Tegyük fel, hogy szeretnénk ha a programunk egy bizonyos számot már rögtön akkor megjelenítene a szövegmezőben, amikor a program betöltődik és az ablak első alkalommal megjelenik. Gyakorlat Az ablakhoz tartozó összes információ egy nib fájlban tárolódik (a nib a NeXT Interface Builder-ből származó rövidítés, ahogy azt korábban már láttuk). Jó ötletnek tűnik, hogy a számunkra szükséges metódust az Application Kit tartalmazhatja. Nézzük meg, hogyan szerezhetünk több információt erről a framework-ről. Az Xcode-ban válasszuk a Documentation-t a Help menüből. Kapcsoljuk be a Full-Text Search opciót a fejlécben. Ezután írjuk be az Application Kit-et a kereső mezőbe és indítsuk el a keresést. Az Xcode több eredményt is ad erre a keresésre. Van közöttük egy dokumentum, aminek a neve: Application Kit Framework Reference. Ezen belül megtalálhatod az általa biztosított szolgáltatásokat. A Protocols alcím alatt található egy link, aminek a neve NSNibAwaking. Ha erre kattintasz, akkor megkapod az NSNibAwaking osztály dokumentációját.
NSNibAwaking Protocol Objective-C Reference (informális protokoll) - 68 -
/System/Library/Frameworks/AppKit.framework Framework Deklarálás helye AppKit/NSNibLoading.h Kisérő dokumentErőforrások beolvasása
Protokoll leírás Ez az informális protokoll egy egyszerű metódusból áll: awakeFromNib. Ezt a metódust az osztályok arra tudják használni, hogy alapállapotokat állítson be közvetlenül azután, hogy az objektumok betöltődtek egy Interface Builder archívumból (nib fájl).
Amennyiben beillesztjük ezt a metódust, ez akkor lesz meghívva, amikor az objektumunk betöltődött az ő nib fájljából. Ezért ezt pontosan arra tudjuk használni, ami a célunk: megjeleníteni egy értéket a szövegmezőben a betöltődés pillanatában. Természetsen nem azt akarom ezzel mondani, hogy mindig ilyen egyszerűen meg lehet találni a helyes metódust. Gyakran sokkal több keresésre és a keresőszavak kreatív használatára van szükség annak érdekében, hogy megtaláljuk az ígéretes metódust. Ennek érdekében elengedhetetlenül szükséges, hogy minél alaposabban megismerd mindkét framework dokumentációját, hogy legyen egy áttekintésed arról, milyen osztályok és metódusok állnak a rendelkezésedre. Természetesen lehetetlen egyszerre mindent megismerni, de minél többet tudsz , annál nagyobb segítség ez számodra, hogy megtervezd a programod működését. Nos, megtaláltuk a szükséges metódust, nincs más dolgunk, mint ennek a beillesztése a MAFoo.m implementaciós fájlba [1.15]. //[1] #import "MAFoo.h" @implementation MAFoo - (IBAction)reset:(id)sender { [textField setIntValue:0]; } - (IBAction)setTo5:(id)sender { [textField setIntValue:5]; } - (void)awakeFromNib // [1.15] { [textField setIntValue:0]; } @end
Amikor az ablak megnyílik, az awakeFromNib metódus automatikusan meghívásra kerül. Ennek eredményeképpen a szövegmezőben megjelenik egy nulla kezdőérték. - 69 -
11: Pointerek BecomeAnXcoder - 11: Pointers
Pointerek
Figyelmeztetés!
Ez a fejezet haladó témát tartalmaz és a C nyelv alapvető fogalmait tárgyalja, ami kezdőknek félelmetes lehet első olvasásra. Nem kell megijedni, ha itt nem sikerül mindent megérteni. Szerencsére ahhoz, hogy elkezdjünk programozni az Objective C nyelvben, nem szükséges maradéktalanul megérteni az egyébként hasznos területet: hogyan működnek a pointerek (mutatók). Bevezető Amikor definiálsz egy változót, a Mac lefoglal egy memóriaterületet, ahol majd ennek a változónak a tartalmát fogja tárolni. Példaként nézzük a következő utasítást: //[1] int x = 4;
Ahhoz, hogy futtatni lehessen ezt a kódot, a gépednek kell találnia egy kis üres helyet a memóriában és meg kell jelölnie, hogy ez az a terület ahol az x nevű változó értéke tárolva lesz (természetesen tudnánk és tanácsos is ennél beszédesebb változónevet választani). Vessünk egy pillantást még egyszer az [1]-es utasításra. A változó típusának megadása (itt most int) meghatározza, hogy mekkora területet kell az x változó számára lefoglalni. Amennyiben ez a típus long long vagy double lenne, akkor több helyre lenne szükség. Az x = 4 értékadó utasítás a 4 értéket elhelyezi a lefoglalt területre. Természetesen a számítógéped emlékezni fog arra, hogy az x nevű változó értékét a memória mely területére helyezte el, vagy más szavakkal mondva arra, hogy mi a címe az x változónak. Ezután minden egyes alkalommal, amikor használjuk az x változót a programunkban, a számítógéped meg fogja találni a megfelelő helyen (a megfelelő címen) az x változó aktuális értékét. A pointer (mutató) egyszerűen egy változó amelyik egy másik változó címét tartalmazza. Változó hivatkozások
- 70 -
Egy változó címét megkaphatjuk, ha a neve elé egy & jelet írunk. Például a &x megadja az x címét. Amikor a számítógép kiértékeli az x kifejezést, akkor az x változó értékével tér vissza (példánkban ez a visszatérési érték a 4). Ezzel szemben, amikor a &x kifejezést értékeli ki, akkor az x változó címével tér vissza, és nem az ott tárolt értékkel. A cím egy olyan szám, amelyik egyértelműen megjelöli a memória egy adott szeletét (mint ahogy a szobaszám is egyértelműen megjelöl egy szállodai szobát). Pointerek használata Egy pointert a következőképpen deklarálhatunk: //[2] int *y;
Ez az utasítás definiál egy y nevű változót, amelyik tartalmazni fogja egy egész típusú változó címét. Még egyszer: ez nem egy egész típusú változót tartalmaz, hanem annak a címét. Ahhoz, hogy az y változó tartalmazza az x változó címét (másképpen mondva: az yhoz hozzárendeljük az x címét ), a következőt kell írni: //[3] y = &x;
Most y az x címére "mutat", azaz azt tartalmazza. Az y segítségével ezért megkaphatjuk x tartalmát is, méghozzá a következőképpen. Ha egy pointer neve elé egy csillagot írunk, akkor megkapjukannaka változónak az értékét, amire a pointer mutat. Például ha kiértékeljük a *y
kifejezést, akkor ez a 4 értéket fogja szolgáltatni. Ez ugyanaz, mintha az x kifejezést értékeltük volna ki. Végrehajtva a következő utasítást: *y = 5
ez egyenértékű azzal, mintha az x = 5
utasítást hajtottuk volna végre. A pointerek nagyon hasznosak, mivel időnként nem egy változó értékére, hanem a változó címére szeretnénk hivatkozni. Például szeretnél írni egy kis programot, ami eggyel megnöveli egy változó értékét. Vajon működni fog a következő programrészlet? //[4] void increment(int x) { x = x + 1; }
- 71 -
Ez bizony sajnos nem fog jó eredményt szolgáltatni! Amennyiben meghívod ezt a függvényt egy programból, akkor nem a várt eredményt fogod megkapni: //[5] int myValue = 6; increment(myValue); NSLog(@"%d:\n", myValue);
Az eredmény 6 lesz. Miért is? Hát nem növeltük meg a myValue változó értékét azzal, hogy meghívtuk az increment függvényt? Nem, ez most nem történt meg. Ugyanis a [4]-ben levő függvény csak átveszi a myValue változó értékét (azaz a 6-ot), megnöveli eggyel, aztán nem csinál vele semmit. A függvények csak az átadott változó értékekkel dolgoznak és nem a változókkal magukkal, amelyek ezeket az értékeket tartalmazzák. Azzal, hogy megnöveled az x értékét (ahogy ez a [4]-ben meg is történik), ezzel csak a függvény által megkapott értéket növeled meg. Minden ilyen változtatás elveszik, ha ebből a függvényből visszatérünk. Sőt itt az x változónak sincs jelentősége, gondold csak végig mit várhatsz eredményül itt az increment(5); függvényhívástól? Amennyiben szeretnél egy működő változatot írni erre a feladatra, tehát egy olyan függvényt, amelyik elfogad változókat bemenetként és megnöveli azok értékét, akkor a változó címét kell átadnod! Ekkor már nem csak használni tudod a változóban tárolt aktuális értéket, hanem meg is tudod változtatni azt. Ehhez egy pointert kell megadnod az argumentumban: //[6] void increment(int *y) { *y = *y + 1; }
és ezt így tudod meghívni: //[7] int myValue = 6; increment(&myValue); // a cím továbbküldése // most már a myValue értéke egyenlő lesz 7-tel
- 72 -
12: Sztringek BecomeAnXcoder - 12: Strings
Sztringek
Bevezető
Már eddig is találkoztunk néhány alapvető adattípussal: int, long, float, double, BOOL. Ezen kívül az előző fejezetben megismertük a pointereket. Sőt érintettük a sztring fogalmát is, méghozzá az NSLog() függvénnyel kapcsolatban. Ennek a függvénynek a segítségével lehetőségünk van karakterek képernyőre való kiíratására és közben még érték behelyettesítést is biztosít a %-jellel kezdődő kódrészletek számára. Ilyen volt például a %d. //[1] float piValue = 3.1416; NSLog(@"Három példa sztringek képernyőre való kiíratására.\n"); NSLog(@"A pi szám közelítő értéke %10.4f.\n", piValue); NSLog(@"A dobókocka lapjainak száma %d.\n", 6);
Előre megfontolt szándékkal nem beszéltünk eddig a sztringekről, mint adattípusról. Az egész és lebegőpontos típusoktól eltérően a sztringek valódi objektumok, amelyek vagy az NSString, vagy pedig az NSMutableString osztályból származnak. Ismerkedjünk meg most ezekkel az osztályokkal, kezdjük az NSString-gel. NSString
Ismét pointerek //[2] NSString *favoriteComputer; favoriteComputer = @"Mac!"; NSLog(favoriteComputer);
//[2.1] //[2.2]
Valószínűleg a második utasítást érthetőnek találod, de az elsőt [2.1] nem árt egy kicsit megmagyarázni. Emlékszel arra, hogy amikor egy pointer változót deklaráltunk, akkor meg kellett adni, hogy milyen típusú változóra mutat? Emlékeztetőül megismételünk egy ilyen utasítást a 11. fejezetből [3]. - 73 -
//[3] int *y;
Itt megmagyarázzuk a fordítóprogramnak, hogy az y pointer változó egy egész típusú változó helyét mutatja meg a memóriában. Hasonlóan, a [2.1] utasításban megmondjuk a fordítóprogramnak, hogy a favoriteComputer pointer változó egy olyan memória szeletre mutat, ahol egy NSString típusó változó található. Azért használunk pointer változót az Objective-C-ben a sztringek ábrázolására, mert az objektumokkal sohasem lehet közvetlenül műveleteket végezni, mindig csak pointereken keresztül tudjuk elérni őket. Semmi gond, ha ez most még nem teljesen tiszta. Ami viszont fontos, az az, hogy a * jelőlés mindig az NSString, vagy az NSMutableString (vagy egy objektum) egy példányára utal. The @ symbol Ok, de miért jelenik meg ez a mulatságos @ jel állandóan? Nos, az Objective C a C-nyelvnek egy kiterjesztése, és az utóbbi rendelkezik néhány sajátossággal a sztringek kezelése terén. Annak érdekében, hogy ettől teljes mértékben megkülönböztessük az Objective C által használt objektum alapú sztringeket, ezt az @ jelet használjuk. Egy új típusú sztring Mennyiben fejlesztette tovább a C nyelvben használatos sztring fogalmát az Objective C? Nos, az Objective C-ben Unicode alapú sztringekkel találkozunk a korábbi ASCII sztringek helyett. Az Unicode-sztringek alapvetően mindenfajta nyelvben előforduló karaktereket képesek ábrázolni, mint például a kínai és ugyanígy a román abc betűit.
Gyakorlat Természetesen a sztring karakterek deklarálása és értékadása is történhet egy lépésben [4]. //[4] NSString *favoriteActress = @"Julia";
A favoriteActress pointer változó a memóriának arra a szeletére mutat, ahol a "Julia" karaktereket reprezentáló objektum tárolva van. Amint értéket adtál egy változónak, például a favoriteComputer-nek, adhatsz neki más értéket, de nem tudod változtatni magukat a karaktereket [5.7], mivel ez az NSString osztálynak egy példánya. Néhány perc és erről részletesebben is beszélünk. //[5] #import int main (int argc, const char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- 74 -
NSString *favoriteComputer; favoriteComputer = @"iBook"; // [5.7] favoriteComputer = @"MacBook Pro"; NSLog(@"%@", favoriteComputer); [pool release]; return 0; }
Lefuttatva ezt a programot, az eredmény a következő lesz: MacBook Pro
Igaz, hogy azt mondjuk, hogy ez a sztring nem változtatható, ettől függetlenül az egész sztringet bármikor kicserélhetjük egy másikra (és ezt meg is tettük). NSMutableString
Az NSString osztály sztringjét immutable-nak nevezzük, mivel nem lehet módosítani. Ez egész pontosan azt jelenti, hogy a sztring karakterei egyenként nem módosíthatók. Miért jó, ha nem lehet módosítani egy sztringet? Nos, egy nem módosítható sztringet az operációs rendszer könnyebben tud tárolni, ezért a program gyorsabb lehet ezáltal. Amikor Objective C programokat fogsz írni, meg fogod tapasztalni, hogy legtöbb esetben valóban nem kell módosítanod a sztringeket amiket használsz. Természetesen vannak olyan esetek is, ahol olyan sztringekkel kell dolgoznod, amiket jó lenne módosítani. Szerencsére van egy másik osztály is, és az ezzel definiált sztringek már módosíthatóak lesznek. Ez az osztály az NSMutableString. Erről részletesen is szó lesz még ebben a fejezetben.
Gyakorlat Először bizonyosodjunk meg arról, hogy megéretetted-e, hogy mit jelent az, hogy a sztringek objektumok. Mivel objektumok, ezért üzenet küldhető számukra. Például küldhetünk egy length (hosszúság) üzenetet [6]. //[6] #import int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int theLength; NSString * foo; foo = @"Julia!"; theLength = [foo length]; // [6.10] NSLog(@"A hosszúság %d.", theLength); [pool release]; return 0; }
A futás eredménye: A hosszúság 6.
- 75 -
A programozók előszeretettel használják a foo és bar változóneveket, amikor valamit magyaráznak. Ezek nem igazán jó nevek, mert nem eléggé beszédesek, ugyanúgy ahogy az x sem az. Azért használjuk most őket, hogy később ismerősek legyenek számodra és ne okozzon fejtőrést, ha az internetes fórumokon találkozol velük. A [6.10]-es sorban a foo objektumnak elküldjük a length üzenetet. A length metódus az NSString osztályban a következőképpen van definiálva:
- (unsigned int)length A címzettben levő Unicode karakterek számával tér vissza.
Lehetőség van az összes karakter kicserélésére, mindegyiknek a nagy betűs változatára [7]. Ennek érdekében egy megfelelő üzenetet kell küldeni a sztring objektumnak, ami nem más, mint az uppercaseString. Javasoljuk, hogy gyakorlásképpen ezt egyedül keresd meg a dokumentációban (ellenőrizd az NSString osztályban található metódusokat). Ennek az üzenetnek a hatására a sztring objektum készít egy új sztring objektumot ugyanazzal a tartalommal, de mindegyik karakter helyett a megfelelő nagybetű fog szerepelni. //[7] #import int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *foo, *bar; foo = @"Julia!"; bar = [foo uppercaseString]; NSLog(@"%@ át lett konvertálva ebbe %@.", foo, bar); [pool release]; return 0; }
A futás eredménye ez lesz:
Julia! át lett konvertálva ebbe JULIA!
Néha hasznos, ha lehetőségünk van arra, hogy módosítsuk egy sztring tartalmát ahelyett, hogy újat készítenénk. Ilyen esetben az NSMutableString osztályhoz tartozó objektummal kell reprezentálnod a sztringet. Az NSMutableString olyan metódusokat szolgáltat, amelyek segítségével meg lehet változtatni egy sztring tartalmát. Például az appendString: metódus az argumentumban átküldött sztringet hozzáfűzi a cél objektumhoz. //[8] #import br />int main (int argc, const char * argv[]) {
- 76 -
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableString *foo; // [8.7] foo = [@"Julia!" mutableCopy]; // [8.8] [foo appendString:@" Boldog vagyok"]; NSLog(@"Itt látható az eredmény: %@.", foo); [pool release]; return 0; }
A futás eredménye a következő lesz:
Itt látható az eredmény: Julia! Boldog vagyok.
A [8.8] sorban a mutableCopy metódus (ami az NSString osztályhoz tartozik) készít és visszaküld egy mutable sztringet az elküldött sztring tartalommal. Ezért a [8.8] sor lefutása után a foo változó egy mutable sztring objektumra mutat, ami a "Julia!" sztringet tartalmazza. És még mindig a pointerekről! A fejezet elején azt állítottuk, hogy az objektumokkal sohasem dolgozunk közvetlenül, mindig pointereken keresztül érjük el őket. Ezért használjuk a pointer jelet a [8.7] sorban is. Amikor az "objektum" szót használjuk az Objective C nyelvben, akkor rendszerint ezalatt azt értjük, hogy "egy objektumra mutató pointer". Az "objektum" szó ilyen értelemben az objektumra mutató pointer rövidítése. Annak a ténynek, hogy az objektumokat mindig pointereken keresztül használjuk, van egy fontos következménye, amit meg kell értened: ugyanannak az objektumnak, ugyanabban az időben több változó is megfeleltethető. Például a [8.7] sor elfutása után a foo változó megfelel egy objektumnak, ami a "Julia!" sztringet ábrázolja, amit a következő ábrán szemléltetünk:
Az objektumokkal mindig pointereken keresztül dolgozunk Most tegyük fel, hogy a foo változó értékét átadjuk a bar változónak a következő módon: bar = foo;
Ennek a műveletnek az eredményeképpen a foo és a bar változó is ugyanarra az objektumra
- 77 -
mutat:
Több változót is hozzárendelhetünk ugyanahhoz az objektumhoz Ebben az esetben, ha küldünk egy üzenetet az objektumnak a foo segítségével (azaz [foo dosomething];) ugyanazt az eredményt fogja szolgáltatni, mintha a bar segítségével tennénk ugyanezt (azaz [bar dosomething];), ahogy ezt a következő példában láthatjuk: //[9] #import int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableString *foo = [@"Julia!" mutableCopy]; NSMutableString *bar = foo; NSLog(@"foo a következő sztringre mutat: %@.", foo); NSLog(@"bar a következő sztringre mutat: %@.", bar); NSLog(@"\n"); [foo appendString:@" boldog vagyok"]; NSLog(@"foo a következő sztringre mutat: %@.", foo); NSLog(@"bar a következő sztringre mutat: %@.", bar); [pool release]; return 0; }
A futás eredménye: foo bar foo bar
a a a a
következő következő következő következő
sztringre sztringre sztringre sztringre
mutat: mutat: mutat: mutat:
Julia! Julia! Julia! boldog vagyok Julia! boldog vagyok
Az objektum-orientált programnyelvek egyik alapvető jellemzője, hogy ugyanarra az objektumra egy időben több helyről is mutathatnak hivatkozások. Megjegyezzük, hogy ezt már a korábbi fejezetekben is használtuk, például a 8. fejezetben a MAFoo objektumra két különböző gomb objektumról is hivatkoztunk.
- 78 -
13: Tömbök BecomeAnXcoder - 13: Arrays
Tömbök
Bevezető Időnként szükség lehet arra, hogy adatok gyűjteményét tárold. Például nézzük azt az esetet, amikor sztringek egy listájával kell dolgoznod. Meglehetősen ormótlan megoldás lenne, ha minden egyes sztringhez egy változót rendelnénk hozzá. Természetesen ennél sokkal kényelmesebb megoldás is van, ezek a tömbök. A tömb objektumok rendezett listája (, vagy még pontosabban objektumra mutató poineterek listája). Egy tömbhöz adhatsz objektumokat, kitörölheted azokat, vagy megkérdezheted a tömböt, hogy mondja meg adott index-hez (azaz egy adott pozíción) melyik objektum van tárolva. Azt is megkérdezheted egy tömbtől, hogy hány elemet tartalmaz. Rendszerint az elemek számolását 1-től szoktuk kezdeni. A tömbök esetében azonban az első elem indexe a 0, a másodiké az 1 és így tovább ...
Példa: egy három sztringet tartalmazó tömb Ebben a fejezetben több példát is fogunk mutatni, amelyekből jól látható lesz, hogyan működik a 0-val kezdődő elemszámlálás. Két osztályból származtatunk tömböket: NSArray és NSMutableArray. Ugyanúgy, mint a sztringek esetében itt is van mutable és immutable változat. Ebben a fejezetben a mutable verziót fogjuk tárgyalni. Ezek Objective C és Cocoa specifikus tömbök. Létezik egy egyszerűbb fajtája is a tömböknek a C nyelvben (ami természetesen használható az Objective C-ben is), de ezt most nem tárgyaljuk. Most csak megemlítjük ezt arra az esetre, ha később valahol olvasol a C tömbökről és fontos, hogy megértsd, hogy ennek nincs sok köze az NSArrays, vagy az NSMutableArrays-hoz.
- 79 -
Egy osztály metódus A tömbkészítés egyik lehetséges útja, ha végrehajtunk egy ilyen utasítást: [NSMutableArray array];
Ha ezt a kódot lefuttatjuk, akkor generál és visszaad egy üres tömböt. De ... várjunk csak egy kicsit ... ez a kód eléggé furcsának tűnik, nem gondolod? Valóban itt egy osztálynevet: NSMutableArray használtunk az üzenet címzettjének megjelölésekor. Viszont idáig üzeneteket mindig példányokhoz küldtünk, nem pedig osztályokhoz. Ugye? Nos, most ismét valami újat tanultunk: azt a tényt, hogy az Objective C-ben osztályokhoz is tudunk üzeneteket küldeni (aminek az a magyarázata, hogy az osztályok is objektumok, úgynevezett meta-osztályok példányai, de ezt a kérdést nem fogjuk tovább boncolgatni ebben a bevezető tanulmányban). Meg kell említeni, hogy ez az objektum automatikusan kikapcsolásra kerül, amikor elkészült; ez azt jelenti, hogy hozzá lett csatolva egy NSAutoreleasePool-hoz, és be lett állítva az őt létrehozó osztály metódus által kikapcsolásra. Az osztály metódus meghívása egyenértékű a következővel: NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease];
Abban az esetben, ha azt akarod, hogy a tömb hosszabb ideig éljen mint az automatikus kikapcsolás tartama, akkor el kell küldeni egy -retain üzenet példányt. A Cocoa dokumentációban az osztályokon hívható metódusokat úgy jelöljük, hogy egy plusz jellel ("+") kezdjük őket a "-" jel helyett, amit a metódusok előtt már többször láttunk (péládul a 8. fejezetben [4.5]). A dokumentációban a tömb metódusra a következő leírást olvashatjuk: array + (id)array Készít és visszaad egy üres tömböt. Ez a metódus az NSArray mutable alosztályain használható. Lásd még: + arrayWithObject:, + arrayWithObjects:
Gyakorlat Térjünk vissza a programíráshoz. A következő program készít egy üres tömböt, elraktároz benne három sztringet és ezután kiírja a tömb elemszámát. //[1] #import int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *myArray = [NSMutableArray array];
- 80 -
[myArray addObject:@"első sztring"]; [myArray addObject:@"második sztring"]; [myArray addObject:@"harmadik sztring"]; int count = [myArray count]; NSLog(@"Összesen %d elem van az én tömbömben", count); [pool release]; return 0; }
Ezt lefuttatva azt kapjuk, hogy:
Összesen 3 elem van az én tömbömben
A következő program megegyezik az előzővel azzal a kivétellel, hogy a tömb 0 indexű elemét írja ki. Ezt a sztringet az objectAtIndex: metódus segítségével kapja meg [2.13]. //[2] #import int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *myArray = [NSMutableArray array]; [myArray addObject:@"első sztring"]; [myArray addObject:@"második sztring"]; [myArray addObject:@"harmadik sztring"]; NSString *element = [myArray objectAtIndex:0]; // [2.13] NSLog(@"A tömb 0 indexű eleme: %@", element); [pool release]; return 0; }
Futás után ezt látjuk a képernyőn:
A tömb 0 indexű eleme: első sztring
Gyakran szükség lehet arra, hogy végig vegyük egy tömb összes elemét annak érdekében, hogy valamit csináljunk velük. Ezt például egy ciklus segítségével tehetjük meg amelyik kiírja a tömb összes elemét az indexek alapján sorba véve őket: //[3] #import int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *myArray = [NSMutableArray array]; [myArray addObject:@"első sztring"]; [myArray addObject:@"második sztring"]; [myArray addObject:@"harmadik sztring"]; int i; int count; for (i = 0, count = [myArray count]; i < count; i = i + 1) { NSString *element = [myArray objectAtIndex:i]; NSLog(@"A tömb %d indexű eleme: %@", i, element); } [pool release]; return 0; }
A futás eredménye most ez lesz: - 81 -
A tömb 0 indexű eleme: első sztring A tömb 1 indexű eleme: második sztring A tömb 2 indexű eleme: harmadik sztring
Természetesen a tömbök nem csak sztringeket, hanem bármilyen objektumot tartalmazhatnak. Az NSArray és NSMutableArray osztályok további metódusokat szolgáltatnak, és csak bátorítani tudunk arra, hogy tanulmányozd ezeket a dokumentációban, annak érdekében, hogy egyre többet megtudjál a tömbökről. Ezt a fejezetet egy olyan metódus bemutatásával zárjuk, amelyik lehetővé teszi, hogy egy adott indexű tömbelemet kicseréljünk egy másik objektumra. Ennek a metódusnak a neve: replaceObjectAtIndex:withObject:. Idáig olyan metódusokkal találkoztunk amelyek legfeljebb egy argumentummal rendelkeztek. Ez most különbözik ezektől és éppen ezért beszélünk még róla: itt most két argumentum van. Észreveheted, hogy a nevében két kettőspont is van. Az Objective C metódusai akárhány argumentummal is rendelkezhetnek. Most megmutatjuk, hogyan kell ezt a metódust használni: //[4] [myArray replaceObjectAtIndex:1 withObject:@"Helló"];
A metódus futtatása után az 1-es indexű objektum a @"Helló" sztring lesz. Természetesen csak egy létező indexre működik ez a metódus. Azaz a megadott indexű elem helyén már kell lennie egy tárolt objektumnak annak érdekében, hogy az objektum képes legyen azt kicserélni a küldött objektummal.
Következtetés Láthatod, hogy az Objective C-ben a metódus nevek olyanok, mint a lyukas mondatok (kettősponttal szabdaltak). Amikor meghívsz egy metódust és a lyukakat aktuális értékekkel töltöd ki, egy értelmes "mondatot" kapsz eredményül. Ez a fajta metódus név jelölés és hívás a Smalltalk-ból lett átvéve és az egyik legnagyobb erőssége az Objective C-nek, mivel a kódot nagyon kifejezővé teszi. Amikor majd a saját metódusaidat tervezed, akkor igyekezned kell olyan nevet választani nekik, hogy amikor meghívod őket, az kifejező mondatokban történhessen. Ez olvashatóbbá teszi az Objective C kódodat, ami rendkívül fontos annak érdekében, hogy a programod könnyen továbbfejleszthető legyen.
- 82 -
14: Tulajdonságok és kellékek BecomeAnXcoder - 14: Accessors and properties
Tulajdonságok és kellékek
Bevezető Láttuk, hogy egy objektum lehet látható, mint egy ablak, vagy egy szövegmező, vagy pedig lehet láthatatlan, mint egy tömb, vagy egy vezérlő, amelyik a felhasználói felület műveleteit felügyeli. Tehát akkor mi is valójában az objektum? Az objektum alapvetően értékeket (változókat) tartalmaz és végrehajt akciókat (metódusokat). Egy objektum adatokat tartalmaz és műveleteket végez velük. Egy objektum képes úgy működni, mint egy kis számítógép: üzeneteket küld és fogad. A programod végül is ezeknek a kis számítógépeknek a hálózata, és ezek együttműködésének köszönhetően kapjuk meg a kívánt eredményt. Objektum összeállítás A Cocoa programozónak az a feladata, hogy osztályokat készítsen, amik egy sor további objektumot tartalmaznak (sztringeket, tömböket, szótárakat) és ezek olyan értékeket tárolnak, amelyeket az osztály használ működése közben. Néhány objektum ezek közül egy egyszerű metódus keretében lesz elkészítve, használva, aztán eldobva. Mások végig kísérik az objektum életét. Ez utóbbiak a példányváltozók (instance variable), vagy az osztály tulajdonságai (property). Létezhetnek olyan metódusok is amelyek ezekkel a váltózókkal dolgoznak. Ezek összeállítását nevezzük objektum készítésnek (object composition). Az így készített objektumok jellemzően az NSObject-ből öröklődnek. Például a kalkulátor vezérlő (controller) osztály tartalmazhatja következő példányváltozókat: a gomb objektumok egy tömbje, az eredmény szövegmező változó. Tartalmazhat továbbá metódusokat: szorzás, összeadás, kivonás, osztás és a grafikus felhasználói felületen való megjelenítés. A példányváltozókat a header fájlban deklaráljuk. A kalkulátor alkalmazás vezérlő osztálya például így nézhet ki: //[1] @interface MyCalculatorController : NSObject { //Instance variables (példányváltozók) NSArray * buttons; NSTextField * resultField; } //Metódusok - (NSNumber *)mutiply:(NSNumber *)value;
- 83 -
- (NSNumber *)add:(NSNumber *)value; - (NSNumber *)subtract:(NSNumber *)value; - (NSNumber *)divide:(NSNumber *)value; @end
Egységbezárás Az objektum-orientált programozás egyik célja az egységbezárás (encapsulation): minden osztály legyen önálló és újra felhasználható amennyire csak lehet. Ezen kívül, ahogy a 3. fejezetben már beszéltünk róla, a változók rejtettek a ciklusokban, függvényekben, metódusokban. A változók elrejtése az objektumokra is érvényes. Ez azt jelenti, hogy a példányváltozókat más objektumból nem lehet elérni, azokat csak maga a metódus látja. Nyílván van olyan eset, amikor egy másik objektum változóját kellene módosítani az adott objektumból. Hogyan lehetséges ez? Vannak metódusok, amelyek kívülről is elérhetők egy objektum számára. Emlékeztetünk, hogy mindössze annyit kell ehhez tenni, hogy küldeni kell egy üzenetet az objektumnak, hogy hajtsa végre azt a metódust. Ez azt jelenti, hogy azzal tehetjük elérhetővé a példányváltozót, hogy készítünk egy metódus párt amelyik eléri és módosítja a példányváltozót. Az ilyen metódusokat együttesen úgy hívjuk, hogy accessor (kiegészítő) metódusok. A 8. fejezetben találkoztunk a setIntValue: metódussal az NSTextField osztályból. Ez a metódus az intValue ellenpéldánya. Együttesen két accessor metódusát alkotják az NSTextField osztálynak. Accessor-ok Nos hogyan néz ki ez a programkódban? Tanulmányozzuk a következő példát. //[2] @interface MyDog : NSObject { NSString * _name; //[2.2] } - (NSString *)name; - (void)setName:(NSString *)value; @end
Ez az osztály interfész definiál egy objektumot: MyDog. A MyDog-ban van egy példányváltozó, egy sztring, aminek a neve: _name [2.2]. Annak érdekében, hogy ki tudjuk olvasni a MyDog _name változójának a tartalmát, vagy meg tudjuk azt változtatni, definiálni kell két accessor metódust: name és setName:. Ezzel a header fájllal meg is lennénk. Az implementációs fájl pedig a következőképpen néz ki: //[3] @implementation MyDog - (NSString *)name { return _name; //[3.3] } - (void)setName:(NSString *)value { _name = value; //[3.6] } @end
- 84 -
Az első metódusban [3.3] egyszerűen visszaadjuk a példányváltozó tartalmát. A második metódusban [3.6] beállítjuk a példányváltozó értékét arra, amit átküldtünk neki. Megjegyzendő, hogy az egyszerűbb érthetőség érdekében leegyszerűsítettük az implementációt. Általában szükség van némi memória kezelés beállításra is ezekben a metódusokban. A következő példa egy sokkal valósághűbb beállítását mutatja az accessoroknak: //[4] @implementation MyDog - (NSString *)name { return [[_name retain] autorelease]; } - (void)setName:(NSString *)value { if (_name != value) { [_name release]; _name = [value copy]; } } @end
Nem megyünk most bele az extra részek mélységeibe (majd a 15. fejezetben lesz erről szó), de első pillantásra látható, hogy ugyanaz a keret, mint a [3]-as példában, csupán egy kis másolás, megörzés és felszabadítás az, amivel bűvült. A különböző adattípusok különböző memória kezelést igényelnek. (Ezen kívül még meg kell itt jegyezni, hogy nem tanácsos tabulátor jellel kezdeni egy példányváltozó nevét, amit itt most megtettünk az egyszerűbb érthetőség érdekében. A saját programkódodban nyugodtan meghívhatod a "name" változót. Mivel a metódusok és a változók nevei más-más névterületen vannak tárolva, ez nem fog konfliktust okozni.) Tulajdonságok A Leopard és az Objective C 2.0 új nyelvi jellemzőket vezetett be a gazdaságosabb programozhatósági feltételek érdekében. Ez pedig nem más, mint a tulajdonságok összeadhatósága. Az accessor-ok közös tulajdonságait kihasználva jelentősen le lehet rövidíteni a programkódot. Kevesebb programkód pedig gyorsabb hibakeresést jelent. :) Tehát miben különböznek a tulajdonságok az accessor-októl? Alapvetően, a tulajdonságok közvetlenül szintetizálják az accessor-okat azáltal, hogy a leghatékonyabb memória kezelést használják. Más szavakkal mondva, megírják számodra az accessor metódusokat, de a háttérben, ami azt jelenti, hogy neked soha sem kell a kóddal foglalkozni. A fenti [2]-es példát Objective-C 2.0-ben a következőképpen írhatjuk meg: //[5] @interface MyDog : NSObject { NSString * name; } @property (copy) NSString *name; @end
Az implementációs fájl pedig így néz ki: //[6] @implementation MyDog
- 85 -
@synthesize name; @end
Ez logikailag ekvivalens a [4]-gyel. Ahogy látszik, kicsit leegyszerűsödött a programkód. Amennyiben az osztályod sok példányváltozója igényel accessor-t, el tudod képzelni mennyire leegyszerűsíti ez az életedet!
- 86 -
15: Memóriakezelés BecomeAnXcoder - 15: Memory Management
Bevezetés
Bevezető Több fejezetben is sajnálkoztam, hogy nem magyarázok meg példákban szereplő utasításokat. Ezek mind memóriakezelő utasítások voltak. Fontos megérteni, hogy a te programod nem az egyedüli program a Macintosh-on és a RAM nem egy olcsó árucikk. Ezért amint a programodnak már nincs szüksége memória területre, akkor azt vissza kell szolgáltatnod a rendszernek. Amikor a mamád megtanított arra, hogy udvariasnak kell lenned és harmóniában élned a közösséggel, akkor éppen azt tanította meg, hogy hogyan kell programozni! Még akkor is ha csupán a te programod futna a számítógépen, fájdalmasan hazavághatja a programodat a fel nem szabadított memória és alaposan lelassíthatja a számítógépedet. Szemétgyűjtés Hivatkozás-számlálásnak (Reference Counting) nevezzük a Cocoa által használt memóriakezelési technikákat amiket bemutatunk ebben a fejezetben. Sokkal részletesebb magyarázatokat találhatsz ezekről a technikákról haladóbb könyvekben, cikkekben (lásd a 16. fejezetet). A Mac OS X 10.5 Leopard bevezetett az Objective C számára egy új technikát, amit Szemétgyűjtésnek (Garbage Collection) nevezünk. A szemétgyűjtés technikája arra szolgál, hogy a rendszer automatikusan kezeli a memóriát és megszünteti a Cocoa objektumok megtartásának, felszabadításának a szükségességét. A szemétgyűjtés varázslata minden NSObject, vagy NSProxy-ból öröklött Cocoa objektumra működik és lehetővé teszi a programozók számára, hogy rövidebb programkódokat írjanak, mint az Objective C korábbi verzióiban. Nem is kell erről tovább beszélni, felejts el minden mást ami ebben a fejezetben olvasható! A szemétgyűjtés bekapcsolása Mivel egy új Xcode projektben a szemétgyűjtés alapból ki van kapcsolva, nekünk kell azt bekapcsolni. A bekapcsoláshoz válaszd ki a Target alkalmazást a forrás listából és nyisd ki az Inspector-t. Válaszd ki az "Enable Objective-C Garbage Collection" opciót. Hivatkozás-számlálás: Az objektum életciklusa
- 87 -
Ha érdekelnek a Leopard előtti memória kezelési technikák, akkor olvasd ezt tovább. Amikor a programod készít egy új objektumot az elfoglal egy bizonyos memóriaterületet és neked ezt fel kel szabadítani amikor az objektumra már nincs tovább szükséged. Azaz, ha az objektum már nincs használatban, akkor el kell dobni. Azonban nem mindig könnyű megállapítani, hogy pontosan mikor is fejeződik be egy objektum használata. Például a program futása során az adott objektumra további objektumok hivatkoznak, ezért addig nem szabad eldobni amíg lehetséges, hogy valamelyik másik objektumnak szüksége lehet rá (ha megpróbálunk egy már eltakarított objektumot használni, attól lefagyhat az egész program, vagy egészen váratlan működéshez vezethet). A megtartási szám A Cocoa úgy próbál segíteni abban, hogy mikor dobhatsz el egy objektumot, hogy minden objektumhoz hozzárendel egy számot, amit a nevének megfelelően az objektum megtartási számának neveznek. A programodban, amikor eltárolsz egy objektum referenciát akkor tudatni kell az objektummal, hogy meg kell növelnie a megtartási számot eggyel. Amikor eltörölsz egy objektum hivatkozást, akkor tudatni kell vele, hogy csökkentse a megtartási számot eggyel. Amikor egy objektum megtartási száma eléri a nullát, akkor az objektum tudni fogja, hogy már nincsen semmilyen hivatkozási kapcsolata és biztonságosan eldobható. Az objektum ezután eltakarítja magát és felszabadítja a megfelelő memória részt. Példaként feltételezzük, hogy digitális zenedoboz alkalmazáson dolgozol ami zeneszámokat és lejátszási sorrendeket ábrázoló objektumokat tartalmaz. Feltételezzük, hogy egy adott zeneszám objektum három sorrend objektumhoz van hozzárendelve. Ha máshova nincs hozzárendelve, akkor a zeneszám objektumod megtartási értéke három.
Az objektum tudja, hogy hány alakalommal hivatkoznak rá, a megtartási számnak köszönhetően
- 88 -
Megtartás és felszabadítás Egy objektum megtartási számát úgy tudod növelni, hogy küldesz neki egy megtartási üzenetet. [anObject retain];
Egy objektum megtartási számát úgy tudod csökkenteni, hogy küldesz neki egy felszabadítási üzenetet. [anObject release];
Automatikus felszabadítás A Cocoa ezen kívül biztosít számodra egy automatikus felszabadítási készletet, amelyik lehetővé teszi a késleltetett felszabadítási üzenet küldését, nem rögtön, hanem valamikor később. Ehhez csak annyit kell tenned, hogy regisztrálod az objektumot egy automatikus felszabadítási készletbe azzal, hogy elküldöd az autorelease üzenetet. [anObject autorelease];
Az automatikus felszabadítási készlet felügyeli a késleltetett felszabadítási üzenetet az objektumodhoz. Az eddigi programjainkban már találkoztunk a késleltetett felszabadítási készletet kezelő utasításokkal, amik segítségével utasítjuk a rendszert a korrekt felszabadítási készlet működtetésre.
- 89 -
16: Információforrások BecomeAnXcoder - 16: Sources of Information
Információforrások
Jelen könyvnek az volt a szerény célja, hogy megtanítsa az Objective C alapjait az Xcode környezetben. Ha legalább kétszer elolvastad és kipróbáltad a példákat a saját igényeidnek megfelelően, akkor már felkészültél arra, hogy megtanuld hogyan tudsz majd saját programokat írni. Megkaptad az alapokat ahhoz, hogy elkezdhesd a programozást, de ne felejtsd el még tanulmányozni az alábbi linkek mögött levő tartalmakat. Egy fontos tanács mielőtt programozni kezdesz: ne siesd el, először tanulmányozd a framework-öket, mivel lehetséges, hogy az Apple már elvégezte helyetted a munkát, vagy olyan osztályokat biztosít számodra, amelyekkel minimális kódírással összeállíthatod a programodat. Vagy az is lehet, hogy valaki más már megírta amire neked szükséged van és a forrást elérhetővé tette számodra valahol. Tehát érdemes tanulmányozni a dokumentációkat és körülnézni az interneten is. Mindenekelőtt feltétlenül látogasd meg az Apple fejlesztői oldalát: http://developer.apple.com Melegen javasoljuk a következő oldalak tanulmányozását: http://www.cocoadev.com http://www.cocoadevcentral.com http://www.cocoabuilder.com http://www.stepwise.com http://developer.apple.com/mac/library/documentation/General/Conceptual/DevPediaCocoaCore/index.html A fenti oldalak további hasznos, fontos információkat tartalmazó oldalakra mutató linkeket is tartalmaznak. Érdemes feliratkozni a cocoa fejlesztői levelezési listára cocoa-dev mailing list. Ez az a hely ahol felteheted a kérdéseidet. Segítőkész emberek a legjobb tudásuk szerint fognak válaszolni neked. Legyél türelmes és körültekintő és mielőtt kérdeznél valamit próbáld megkeresni kérdésedre a választ az arhívumban: http://www.cocoabuilder.com. Hasznos tanácsokat kapsz a Hogyan tedd fel okosan a kérdésedet oldalon. Több nagyon jó könyvet is ajánlhatunk a Cocoa programozásról. A Stephen Kochan által írt Programozás Objective C-ben című könyv kezdőknek szól. Sok további könyv feltételezi az ebben található alapismereteket. Nekünk különösen tetszett Aaron Hillegas könyve: Cocoa - 90 -
programozás Mac OS X alatt. Aaron a Big Nerd Ranch-on tanítja az Xcode használatát. Végül még szeretnénk egy fontos dologra felhívni a figyelmet. Mac OS alatt programozol. Mielőtt publikussá teszed a programodat ne csak arra figyelj, hogy az hibamentes legyen, de ezen kívül nézzen ki jól és megfeleljen az Apple által megkövetelt követelményrendszernek. Amennyiben ezeket a feltételeket teljesíted, már semmi sem tarthat vissza a publikálástól! Vedd figyelembe mások véleményét, ami sokat segíthet a programod továbbfejlesztésében. Reméljük, hogy élvezted ezt a könyvet és segíteni tudtunk abban, hogy elindulj az Xcode programozás világában! Bert, Alex, Philippe Licensz: Creative Commons Attribution 3.0 Licensz http://creativecommons.org/licenses/by/3.0/deed.hu
Version 1.15 - 2008. Február 19. Fordítás: Major Zoltán, 2009. szeptember Web: http://xcode.major-world.com/ PDF dokumentum: Kovács Lajos Krisztián, 2010. április Web: http://alkalmazasfejlesztes.blogspot.com/
- 91 -