Tässä olisi sitten pikaopas Objective-C kieleen tarjolla. Objective-C on C-kielen laajennos, johon on yhdistetty olio-ominaisuuksia Smalltalk-kielestä. Objective-C:tä käytetään pääasiassa Cocoa-ohjelmien kehittämiseen Mac OS X -käyttöjärjestelmään. GCC tukee kuitenkin obj-c koodin kääntämistä muillakin alustoilla, mutta tätä en ole itse sen tarkemmin kokeillut.
Tämän oppaan olisi tarkoitus selittää obj-c:n perusrakenne. Oletan kuitenkin, että sinulla on hieman kokemusta C-kielestä ja olio-ohjelmoinnista.
Objective-C on C++:n ja javan tavoin olio-kieli, joten alkuun olisi hyvä kertoa miten olioita käsitellään.
Olioilmentymiin viitataan osoittimilla. Ensin oliolle on varattava tilaa vapaasta muistista alloc-luokkametodilla:
NSMutableArray *taulukko = [NSMutableArray alloc];
Nyt meillä on olemassa muistipaikan osoite, johon mahtuu yksi NSMutableArray-tyyppinen olio. Oliota ei kuitenkaan ole vielä olemassa, joten olion konstruktori-metodia pitää kutsua:
[taulukko init];
Tämän kaiken voi tietenkin tehdä yhdellä rivillä:
NSMutableArray *taulukko = [[NSMutableArray alloc] init];
Olion luomisen yhteydessä näimmekin jo miten luokan metodia ja olion parametritöntä metodia kutsutaan. Metodille voi tietenkin antaa myös parametrejä. Kutsutaan taulukko-olion addObject-metodia ja annetaan parametriksi tavara-olio:
[taulukko addObject:tavara];
Parametrejä voi olla myös useampia. Syntaxi näyttää hieman monimutkaiselta:
[taulukko insertObject:tavara atIndex:4];
Koodista saadaan hieman luettavampaa, jos eri paremetrin laitetaan eri riveille:
[taulukko insertObject:tavara
atIndex:4]Objective-C:n uudemmassa 2.0-versiossa, joka julkaistiin Mac OS X 10.5 mukana, on javan tapaan automaattinen muistinhallinta. Vanhemmassa versiossa on "puoliautomaattinen" muistinhallinta, joka perustuu retain-laskuriin.
Jokainen Objective-C-kielen luokka on peritty NSObject-luokasta, joka sisältää retain-count-attribuutin, sekä retain- ja release-metodit. Kun oliolle varataan tilaa alloc-metodilla, retain-laskuri alustetaan arvoon 1, joka ilmoittaa kyseisen olion olevan vielä käytössä.
Kun oliota tarvitaan jossain, kutsutaan retain-metodia:
[taulukko retain];
, joka kasvattaa laskuria yhdellä.
Kun olio tulee tarpeettomaksi, kutsutaan release-metodia:
[taulukko release];
, joka vähentää laskuria yhdellä. Kun laskuri saavuttaa arvon 0, kukaan ei ole enää kiinnostunut oliosta. Olion dealloc-metodia kutsutaan automaattisesta ja olion varaama tila vapautetaan.
retain- ja release-metodeja kutsutaan automaattisesti esimerkiksi silloin, kun olio lisätä NSMutableArray-taulukkoon tai muuhun säilö-olioon.
retain/release-metodian kanssa tulee kuitenkin joskus vastaan ongelmia. Jos metodi esim. palauttaa uuden merkkijono-olion, mihin arvoon retain-count pitäisi asettaa. Mikäli arvoksi jätetään alloc-metodin asettama 1, jää olion vapauttaminen metodin kutsujan tehtäväksi. Tämä tekee paljon turhaa koodia mikäli kutsuja ei ole kiinnostunut paluuarvosta. Tätä varten NSObject-luokassa on autorelease-metodi:
[taulukko autorealease];
Jokaisessa cocoa-ohjelmassa on olemassa NSAutoreleasePool-tyyppinen olio, joka huolehtii tälläisten olioiden automaattisesta vapauttamisesta. Kun olion autorelease-metodia kutsutaan, olio lisätään autoreleasePooliin. Kun ohjelman käyttäjän aloittama toiminto on suoritettu loppuun, autoreleasePool kutsuu jokaisen itseensä lisätyn olion release-metodia. Tällä tavalla käyttämättömät oliot saadaan kätevästi tuhottua melkein kuin itsestään.
Luodaan seuraavaksi yksinkertainen komentorivistä ajettava ohjelma. Koodassa on muutamia asioita, joihin palaan uudelleen oppaan loppuosassa.
Luodaan ensin Henkilo-niminen luokka, jolla on 3 attribuuttia: nimi, osoite ja puhelinnumero. Toteutetaan luokalle konstruktori-metodi ja attributtien get/set-metodit. Luokka on C ja C++ kielien tapaan jaettu kahteen erilliseen tiedostoon: header-tiedosto (*.h) sisältää luokan määritykset ja koodi-tiedoston (*.m) sisältää koodin.
#import <Cocoa/Cocoa.h>
@interface Henkilo : NSObject {
NSString *nimi;
NSString *osoite;
NSString *puhNum;
}
-(id)initWithNimi:(NSString*)nimi osoite:(NSString*)osoite puhelinNumero:(NSString*)numero;
-(NSString*)nimi;
-(NSString*)osoite;
-(NSString*)puhNum;
-(void)setNimi:(NSString*)uusiNimi;
-(void)setOsoite:(NSString*)uusiOsoite;
-(void)setPuhNum:(NSString*)uusiPuhNum;
@end
CGFloat red, greed, blue, alph; [color getRed:&red green:&green blue:&blue alpha:&alph];
, jossa metodille välitetään niiden muuttujien osoitteet, joihin metodin halutaan tallentavan halutut arvot.
#import "Henkilo.h"
// Luokan toteutus tulee @implementation osan sisälle
@implementation Henkilo
// konstruktori-metodi
-(id)initWithNimi:(NSString*)nimi osoite:(NSString*)osoite puhelinNumero:(NSString*)numero
{
// Kutsutaan isä-luokan (NSObject) konstruktori-metodia
self = [super init];
// Alustetaan attribuutit
[self setNimi:nimi];
[self setOsoite:osoite];
[self setPuhNum:numero];
// init-metodi palauttaa osoittimen itseensä
return self;
}
// Tuhoojametodi
-(void)dealloc
{
// Vapautetaan kaikki attribuutit
[nimi release];
[osoite release];
[puhNum release];
}
// Attribuuttien saantimetodit
-(NSString*)nimi
{
return nimi;
}
-(NSString*)osoite
{
return osoite;
}
-(NSString*)puhNum
{
return puhNum;
}
// Attribuuttien set-metodit
-(void)setNimi:(NSString*)uusiNimi
{
// Kasvatetaan uuden arvon retain-laskuria
[uusiNimi retain];
// Vähennetään vanhan arvon retain-laskuria
[nimi release];
// Huomaa järjestys! Mikäli vapauttaisit vanhan arvon ensin ja
// uusiNimi sattuisi osoittamaan samaan NSString-olioon kuin nimi,
// saisit aikaan paljon ongelmia.
nimi = uusiNimi;
}
-(void)setOsoite:(NSString*)uusiOsoite
{
[uusiOsoite retain];
[osoite release];
osoite = uusiOsoite;
}
-(void)setPuhNum:(NSString*)uusiPuhNum
{
[uusiPuhNum retain];
[puhNum release];
puhNum = uusiPuhNum;
}
@end
Foundation Tool -ohjelma on sen verran yksinkertainen, että sen toiminta lähtee tavallisten C-ohjelmien tapaan liikkeelle main-funktiosta. Toteutetaan seuraavaksi main-funktio ja tehdään Henkilo-luokaan vielä pari tarpeellista muutosta.
#import <Foundation/Foundation.h>
#include "Henkilo.h"
int main (int argc, const char * argv[]) {
// Luodaan ensin AutoreleasePool huolehtimaan autorelease-olioista
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Luodaan taulukko henkiloita varten
NSMutableArray *henkilot = [[NSMutableArray alloc] init];
// Luodaan uusi Henkilo-olio
Henkilo *lisattava = [[Henkilo alloc] initWithNimi:@"Matti Meikäläinen"
osoite:@"Testiosoite 13"
puhelinNumero:@"1234567890"];
// Lisätään olio taulukkoon
[henkilot addObject:lisattava];
// Vapautetaan henkilö (taulukko hoitaa retain/release automaattisesti)
[lisattava release];
// Luodaan vielä 2 muuta henkilöä käyttäen samaa väliaikaismuuttujaa
lisattava = [[Henkilo alloc] initWithNimi:@"Testi Testikäyttäjä"
osoite:@"Kaakaotie 55"
puhelinNumero:@"6459394837"];
[henkilot addObject:lisattava];
[lisattava release];
lisattava = [[Henkilo alloc] initWithNimi:@"Aku Ankka"
osoite:@"Testiosoite 14"
puhelinNumero:@"00100010001001"];
[henkilot addObject:lisattava];
[lisattava release];
// Käydään taulukon sisältö läpi ja tulostetaan ne
int i;
for(i = 0; i < [henkilot count]; i++)
// NSLog tulostaa konsoliin (vertaa printf c ja java)
NSLog(@"%@", [henkilot objectAtIndex:i]);
[henkilot release];
// Vapautetaan autoreleasePool
[pool drain];
return 0;
}
// NSObject-luokassa on description-metodi, jonka tehtävänä on muuttaa olio merkkijonoksi
// Uudelleenmääritellään se tähän kohtaa
-(NSString*)description
{
NSString *tulos;
// Kutsutaan NSString luokan-metodia. Palauttaa autorelease-merkkijono-olion
tulos = [NSString stringWithFormat:@"Nimi: %@, Osoite: %@, puhelin: %@", [self nimi], [self osoite], [self puhNum]];
return tulos;
}
// Luokan metodi, joka palauttaa autorelease Henkilo-olion // + -merkki alussa määrittelee metodin luodan metodiksi (vastaa javan static-metodia) +(id)henkiloWithNimi:(NSString*)nimi osoite:(NSString*)osoite puhelinNumero:(NSString*)numero;
// Luokan metodi, joka palauttaa autorelease Henkilo-olion
+(id)henkiloWithNimi:(NSString*)nimi osoite:(NSString*)osoite puhelinNumero:(NSString*)numero
{
Henkilo *uusi = [[Henkilo alloc] initWithNimi:nimi osoite:osoite puhelinNumero:numero];
[uusi autorelease];
return uusi;
}
// Luodaan uusi Henkilo-olio hyödyntäen luokan metodia
Henkilo *lisattava = [Henkilo henkiloWithNimi:@"Matti Meikäläinen"
osoite:@"Testiosoite 13"
puhelinNumero:@"1234567890"];
Kaikki Objective-C-luokat peritään NSObject-luokasta. Objective-C luokat ovat aika monimutkaisia, koska ne sisältävät esim. muistinhallinnan ja useita Cocoaan liittyviä ominaisuuksia. init, retain-, release- ja description- metodeihin tuossa jo tutustuttiinkin. Näiden lisäksi voisi mainita vielä isEqual:-metodin, joka ottaa paremetrikseen toisen olion ja palauttaa true mikäli oliot ovat samat. isEqual on uudelleenmääritelty useiden luokkien kohdalla, esim NSString palauttaa true mikäli merkkijonojen sisältö on sama.
Objective-C:ssä merkkijonot voivat olla joko C-kielen char-taulukoita tai NSString-tyyppisiä olioita. NSString tyyppiset merkkijono esitellään @-merkin kanssa:
NSString *esim = @"Testi merkkijono";
Merkkijono voidaan alustaa myös hieman monipuolisemmin initWithFormat:-metodilla:
NSString *esim = [[NSString alloc] initWithFormat:@"Tämä on %d. opas, jonka kirjoitan %@ -sivustolle", 2, @"macintoast.org";
Tästä syntyy merkkijono: "Tämä on 2. opas, jonka kirjoitan macintoast.org -sivustolle"
length-metodi palauttaa merkkijonon pituuden: [merkkijono length]
NSArray on luokka, johon voidaan tallentaa taulukkomaisesti toisia olioita. Indeksointi alkaa C-taulukon tapaan nollasta. NSArrayn sisältöä ei voi muokata sen luomisen jälkeen. Uusi taulukko luodaan luettelemalla taulukkoon tulevat alkiot:
NSArray *taulukko = [NSArray arrayWithObjects: eka, toka, kolmas, nil];
NSArrayn alkioihin pääsee käsiksi objectAtIndex:-metodilla. Haetaan 3 alkio:
id alkio = [taulukko objectAtIndex:2];
Alkioiden määrän saa selville count-metodilla: int maara = [taulukko count];
NSMutableArray eroaa NSArraystä Mutable-avainsanalla, joka kertoo olion olevan muokattava luomisensa jälkeen. objectAtIndex:- ja count -metodin toimivat samalla tavalla kuin NSArrayssa.
Lisätään uusi alkion NSMutableArray loppuun: [taulukko addObject:alkio];
Lisätään uusi alkion NSMutableArray indeksiin 5: [taulukko insertObject:alkio atIndex:5];
Poistetaan alkio indeksistä 5: [taulukko removeObjectAtIndex:5];
Tyhjätään NSMutableArray: [taulukko removeAllObjects];
NSNumber pitää sisällään numeroarvon. Numeroarvo voi olla tyypiltään signed tai unsigned char, short int, int, long int, long long int, float, double tai jopa BOOL-arvo. Luodaan double-tyyppisen liukuluvun sisältävä NSNumber-olio:
NSNumber luku = [NSNumber numberWithDouble:5.65];
ja kysytään olion sisältämä double-luku:
double a = [luku doubleValue];
NSLog-funktion avulla voidaan tulostaa Objective-C -ohjelmasta konsoliin. Funktio muistuttaa toiminnaltaan paljon C:n ja javan printf-funktioita. NSLog ottaa vastaan merkkijonon ja äärettömän määrän parametrejä: NSLog(@"formatoitava merkkijono", ...);
Esim: NSLog(@"Tämä on %d. opas, jonka kirjoitan %@ -sivustolle", 2, @"macintoast.org");
tulostaa: "Tämä on 2. opas, jonka kirjoitan macintoast.org -sivustolle"
%@ tulostaa olion description-metodin palauttavan NSString-merkkijonon%d tulostaa long-tyyppisen muuttujan%x tulostaa long-tyyppisen muuttujan hexana%o tulostaa long-tyyppisen muuttujan octal-lukuna%f tulostaa double-tyyppisen muuttujan%c tulostaa char-muuttujan merkkinä%s tulostaa null-loppuisen ASCII-merkkijonon%S tulostaa null-loppuisen UNICODE-merkkijononObjective-C sisältää muutaman tietotyypin ja vakion, joita ei löydy sellaisenaan C-kielestä:
Uutta Objective-C 2.0 toi mukanaan muutamia käteviä uudistuksia. Hyödyllisin näistä on varmasti automaattinen muistinhallinta, jonka ansiosta retain/release-sotkun saa jätettyä kokonaan pois. Tällöin täytyy kuitenkin muistaa, että automaattinen muistinhallinta toimii vain Mac OS X 10.5 käyttiksessä. Automaattisen muistinhallinnan saa myös toimimaan C-tyyppien ja taulukoiden kanssa, mutta se vaatii hieman enemmän huomiota. Tästä lisää joskus jossain toisessa oppaassa...
Automaattinen muistinhallinta kytketään päälle ja pois: Project -> Edit Project Settings -> Build / Objective-C Garbage Collection.
Toinen hyödyllinen koodin määrää vähentävä ominaisuus on @property ja @synthesize. Niiden avulla get/set-metodit voidaan totauttaa melkein automaattisesti. Muutetaan Puhelinluettelo käyttämään näitä:
@property (readwrite, retain) NSString *nimi; @property (readwrite, retain) NSString *osoite; @property (readwrite, retain) NSString *puhNum;
@synthesize nimi; @synthesize osoite; @synthesize puhNum;
@property paremetrit:
readonly Toteuttaa vain get-metoditreadwrite Totettuttaa get- ja set-metoditassign Ei toteuta retain/releaseretain Totetuttaa retain/releasecopy Palauttaa get-metodilla kopion attribuutista