Properties in ObjectiveC

Properties in ObjectiveC
Als Properties bezeichnet man die Funktionalität bzw. den Weg, um auf Werte zuzugreifen, die in einem Objekt hinterlegt sind, gewöhnlich auf Instanzvariablen. Natürlich kann man auf Instanzvariablen auch einfach so zugreifen - zumindest innerhalb der Klasse, in der sie deklariert wurden. Es macht aber sehr häufig Sinn, trotzdem immer über die Property auf sie zuzugreifen. Warum, das erklärt dieser Artikel.
Properties sind in ObjectiveC eigentlich nichts anderes, als ein definierter Satz an Methoden. Die Methodennamen folgen dabei folgendem Konzept:
- (<Datentyp>) <propertyName>; // zum Auslesen des Wertes propertyName
- (void) set<PropertyName>:(<Datentyp>)var; // zum Setzen des Wertes propertyName
Hier mal ein Beispiel, wie man Methoden bereitstellen würde, um auf die Instanzvariable int meineZahl; zuzugreifen:
- (int) meineZahl {
        return meineZahl;
}
- (void) setMeineZahl:(int)var {
        meineZahl = var;
}
Man beachte die Groß- und Kleinschreibung des Variablennamens für den Methodennamen! Das gehört zur Art- und Weise, wie solche Propertymethoden gebaut werden!

Natürlich ist das nur die einfachste Variante, wie man Propertymethoden bauen kann. Man kann in solchen Methoden den ganzen Unfug machen, den man auch in normalen Methoden machen kann. So könnte man z.B. Retain und Release beachten, wenn man über die set-Methode (auch Setter genannt) ein Objekt setzt:
- (void) setMeinString:(NSString*)var {
        [var retain];
        [meinString release];
        meinString = var;
}

Und jetzt erkennt man auch schon den Vorteil von Properties: Man muss sich in seinem Programm nicht mehr über das Variablenhandling Gedanken machen, wenn man die Variablen nutzen will. Das erledigen die Propertymethoden.

Um aber statt direkt auf die Variablen zuzugreifen nun über die Property zu gehen, kann man klassisch die Methoden benutzen, z.B.
[meinObjekt setMeinString:@"Hallo Welt"]; // Setzt den Wert @"Hallo Welt"
Aber da tippt man sich ja jedesmal die Finger wund, wenn man auf Variablen zugreifen will! Daher wurde der aus C++ oder auch Java bekannte Punkt-Operator auch in ObjectiveC eingeführt:
meinObjekt.meinString = @"Hallo Welt"; // Setzt den Wert @"Hallo Welt"
Beide Zeilen machen das Gleiche. Um auf eine Variable über die Propertymethode zuzugreifen - den sogenannten Getter - schreib ich wiederum einfach meinObjekt.meinString.

Und es gibt auch eine Hilfe, um Propertymethoden zu erstellen; das kann ObjectiveC nämlich alleine!
@interface MeineKlasse : NSObject {
        NSString* meinString;
}
@property (retain) NSString* meinString;
@end
Dies deklariert die oben beschriebenen Methoden, inklusive der Beachtung aller nötigen Retains und Releases! Das wurde durch die Angabe der entsprechenden Option in den Klammern festgelegt. Damit die Methoden auch generiert werden, schreibt man in der .m-Datei in den @implementation-Bereich einfach folgende Zeile:
@synthesize meinString;

Das ist eigentlich auch schon die ganze Propertyzauberei. Neben der Option retain gibt es noch ein paar weitere Optionen, die man in den Klammern durch Kommata getrennt angeben kann, wodurch die Properties erst ihre ganze Stärke ausspielen können:
readonly: Es wird nur ein Getter erzeugt, kein Setter.
readwrite: Es wird Getter und Setter erzeugt. Dies ist die Standardeinstellung.
assign: Der übergebene Wert wird direkt zugewiesen. Dies ist die Standardeinstellung.
retain: Nur bei Objekten anwendbar. Retain und Release wird beachtet.
copy: Nur bei Objekten anwendbar. Das Objekt wird komplett kopiert, nicht nur der Zeiger gemerkt.
nonatomic: Normalerweise kümmern sich Getter und Setter darum, dass der Zugriff auf die Instanzvariablen möglichst "robust" ist, z.B. wenn mehrere Threads konkurrierend auf einen Getter oder Setter zugreifen. Diese Option schaltet diesen robusten Zugriff ab.
6 Kommentare | Permalink | Trackback-Info


Operatoren, Teil 3

Operatoren, Teil 3
Zum Abschluss unserer kleinen Operatorenreihe möchten wir auf die Reihenfolge eingehen, in der Operatoren abgearbeitet werden. Jeder hat sicher schonmal was von "Punktrechnung vor Strichrechnung" gehört. In C-Dialekten sind ALLE Operatoren in eine Rangfolge einsortiert. Ändern kann man diese Reihenfolge, genau wie in der Mathematik, indem man einzelne Operationen mit normalen Klammern umschliesst ().
Hier also nun eine Liste von Operatoren in der Reihenfolge, in der sie abgearbeitet werden. Operatoren mit höherer Priorität stehen also weiter oben.
Operatoren, die sich in der gleichen Gruppe befinden sind dabei gleichwertig und werden einfach der Reihe nach abgearbeitet. Je nach Gruppe und Funktion des Operators geschieht dies innerhalb der Gruppe von links nach rechts oder von rechts nach links:
Gruppe Operatoren Beschreibung Auswertungsrichtung
1 . Zugriff auf Properties eines Objekts von links nach rechts
++ -- Inkrement/Dekrement nach einer Variablen
2 ! ~ logisches, binäres NOT von rechts nach links
+ - als Vorzeichen
++ -- Inkrement/Dekrement vor einer Variablen
3 * / % Arithmetische Operatoren von links nach rechts
4 + - Arithmetische Operatoren von links nach rechts
5 << >> Schiebe-Operatoren von links nach rechts
6 < <= > >= Vergleichsoperatoren von links nach rechts
7 == != Vergleichsoperatoren von links nach rechts
8 & UND Bitoperator von links nach rechts
9 ^ EXKLUSIV OR Bitoperator von links nach rechts
10 | OR Bitoperator von links nach rechts
11 && logischer UND Operator von links nach rechts
12 || logischer ODER Operator von links nach rechts
13 = Zuweisungsoperatoren von rechts nach links
*= /= %=
+= -=
&= ^= |=
<<= >>=
14 , Komma/Liste-Operatoren von links nach rechts
1 Kommentar | Permalink | Trackback-Info


Operatoren, Teil 2

Operatoren, Teil 2
Im Beitrag Operatoren habt ihr bereits ein paar Operatoren kennengelernt. Es gibt jedoch noch ein paar weitere wichtige Operatoren Dies sind in erster Linie Binäroperatoren und Boolesche Operatoren. Beide haben gewissermaßen was mit den beiden möglichen Zuständen zu tun, die ein Computer auf unterster Ebene ausschließlich kennt: 0 und 1, bzw. true und false.

Binäre Operatoren (Bitweise Operatoren)

Binäre Operatoren verknüpfen Ganzzahlen auf Bitebene miteinander. Die betroffene Ganzzahl (z.B. vom Typ int) muss man sich dabei am besten in binärer Form entsprechend dem Dualsystem vorstellen. Zu dem beispielhaften Code unten ist noch zu sagen, dass es in C-Dialekten keine Möglichkeit gibt, um Binärzahlen direkt darzustellen. Die 0100010-Folgen sind also nur symbolisch zu betrachten und stehen für ihre dezimalen Derivate.
0101010 & 1101100 == 0101000 // Boolesche UND-Verknüpfung
0101010 | 1101100 == 1101110 // Boolesche ODER-Verknüpfung
0101010 ^ 1101100 == 1000110 // Boolesche EXKLUSIV-ODER-Verknüpfung
        ~ 1101100 == 0010011 // Boolesche NICHT-Verknüpfung
0001000 << 2      == 0100000 // Bitverschiebung nach links  (hier um 2 Bits)
0001000 >> 2      == 0000010 // Bitverschiebung nach rechts (hier um 2 Bits)

Wir sparen es uns hier zu erklären, was denn der gute Herr Boole sich für Gedanken bzgl. der Veknüpfung zweier möglicher Zustände gedacht hat. Wenn Dir jedoch die Bedeutung boolescher Operatoren nicht bekannt ist, dann kannst Du sie unter "Bitweise Operatoren" in der Wikipedia nachschlagen. Die Wikipedia kann das in diesem Fall nämlich sehr gut und sehr ausführlich erklären!

Boolesche Operatoren (Logische Operatoren)

Boolesche Operatoren funktionieren im Prinzip genau wie die booleschen Versionen ihrer binären Verwandten, die oben aufgeführt sind. Jedoch betrachten sie die gegebenen Zahlen nicht als Bitfeld, sondern wandeln sie im Gesamten zu true oder false um. Jeder Wert, außer der "0", wird dabei zu true.
true && false == false // Boolesche UND-Verknüpfung
  42 && 1     == true  // ist true, weil 42 und 1 ungleich 0 sind
true || false == true  // Boolesche ODER-Verknüpfung
      ! true  == false // Boolesche NICHT-Verknüpfung

Es ist zu beachten, dass es keinen EXKLUSIV-ODER Operator gibt, wie bei den Binären Operatoren!

Aber: Was nutzen uns die Booleschen Überlegungen? Wir können sie z.B. ganz hervorragend für Kontrollstrukturen einsetzen! Man kann so mehrere Bedingungen durch ein if abfragen:
if (a>0 && b>0) {wenn a UND b größer 0 sind, wird dieser Code ausgeführt}
if (a>0 || b>0) {wenn a ODER b größer 0 sind, wird dieser Code ausgeführt}

Hiermit haben wir die wichtigsten Operatoren angesprochen. In Operatoren, Teil 3 behandeln wir der die Reihenfolge, in der die Operatoren ausgeführt werden. Dies geschieht nämlich genau wie in der Mathematik nicht einfach von links nach rechts (Stichwort: "Punkt- vor Strichrechnung"), sondern geschieht FÜR ALLE Operatoren in einer festgelegten, sinnvollen Reihenfolge.
2 Kommentare | Permalink | Trackback-Info


Retain, Release und Autorelease

Retain, Release und Autorelease
Die Speicherverwaltung von Objekten ist ein großes Thema. Zunächst muss man verstehen, dass man eigentlich nur mit Zeigern, also Verknüpfungen auf Objekte arbeitet, niemals mit den Objekten selbst. Weist man also einem Objekt ein anderes Objekt per Zuweisungsoperator zu (z.B. string2 = string1), dann wird in Wirklichkeit nur der Zeiger kopiert. Beide Variablen verweisen auf das gleiche Objekt.
Das hat nachhaltige Folgen. Schickt man Methoden an das Originalobjekt, dann wirkt sich das genauso auf den zweiten "Zeiger" aus und umgekehrt, genau wie bei einer Datei auf der Festplatte und einem Alias (unter Windows auch 'Verknüpfung' genannt).
Was passiert also, wenn ein Programmteil ein Objekt per release freigibt (siehe Artikel Objekte erstellen) und ein anderer Programmteil greift nun doch nochmal über einen anderen Zeiger auf das Objekt zu? Das Programm stürzt gnadenlos ab!
Um dieses Problem zu umgehen, hat man in Cocoa die Möglichkeit geschaffen einem Objekt zu sagen, dass man es noch benötigt. Gibt dann ein anderer Programmteil das Objekt per release frei, wird es nicht wirklich freigegeben. Es wird erst freigegeben, wenn alle beteiligten Programmteile per release bestätigt haben, dass sie das Objekt nicht mehr benötigen. Die Objekte zählen intern mit, wie oft sie benötigt werden. Den "Anspruch" auf ein Objekt meldet man mit retain an:
NSString* string2; // Einen neuen Zeiger definieren
string2 = string1; // Eine Kopie des Zeigers 'string1' anfertigen
[string2 retain];  // Dem System mitteilen, dass wir 'string2' eine Weile benötigen

[mach_was_sinnvolles];

[string2 release]; // wir geben das Objekt wieder frei

Nochmals zur Verdeutlichung: Man kann das retain oder release oben auch an string1 schicken. Es handelt sich ja um das gleiche Objekt, nur unter einem anderen Namen!
Wie man sicher schnell erkennt, macht ein retain auf ein Objekt meistens nur Sinn, wenn es über mehrere Methoden hinweg erhalten bleiben soll. Innerhalb eines einzelnen Codeblocks kann ich ja schließlich auch den ersten Zeiger benutzen. Hierzu seht ihr in unserem Videocast mehr! Wir behandeln das Thema Speicherverwaltung ausführlich ab der Folge '#007: Speicher, Zeiger und Objekte'.

Alle Methoden, die mit init oder copy beginnen, machen übrigens automatisch ein retain. Hier ist also zum Ende der Benutzung nur noch ein release notwendig.
Auch wenn man ein Objekt als Parameter an ein anderes Objekt übergibt, dann muss sich der "Empfänger" selbst darum kümmern, ob er das Objekt noch eine Weile braucht, zum Beispiel wenn man einen String an ein NSTextField übergibt. Man muss also nicht für andere Objekte mitdenken:
[meinTextFeld setStringValue:string1];
// Hier muss man nicht für das Textfeld mitdenken.
// Das Textfeld macht selbst ein retain auf string1, wenn es den String behalten will.

Weiss man zu Beginn einer Codeblocks bereits, dass man ein selbsterstelltes Objekt nur kurzfristig benötigt, dann kann man auch die Funktion autorelease benutzen und es damit dem System überlassen, dass dem Objekt irgendwann nach Beendigung des aktuellen Codeblocks ein release geschickt wird:
NSString* string1 = [[[NSString alloc] init] autorelease];

Manchmal ist es erwünscht, dass man doch eine komplett neue Kopie eines Objektes erhält, die somit auch nicht mehr mit dem Originalobjekt in Verbindung steht. Hierzu gibt es die Methode copy. Diese Methode kopiert eimal das komplette Objekt in einen neuen Speicherbereich und führt auch schon ein retain aus:
NSString* string1 = @"Hallo Welt!";
NSString* string2 = [string1 copy];
// Erzeugt eine Kopie von string1. Es gibt also nun zwei Objekte.
// Entsprechend wird auch doppelt so viel Speicherplatz verbraucht.

Mit retain und release muss man sehr gut aufpassen! Ein retain zuviel, schon wird der Speicherbereich nicht mehr freigegeben. Ein release zuviel, schon stürzt das Programm ab!
12 Kommentare | Permalink | Trackback-Info


Objekte erstellen

Objekte erstellen
Um ein Objekt programmatisch zu erstellen - egal ob aus einer eigenen Klasse oder aus einer Cocoa-Klasse - muss man zunächst den Speicher dafür reservieren (alloziieren) und es dann Initialisieren. Initialisieren bedeutet, dass das Objekt mit den wichtigsten Daten befüllt wird, so dass es funktionsfähig ist. Das Alloziieren passiert über den Klassennamen, das Initialisieren über das Objekt. Hier ein Beispiel:
NSString* string1;          // einen Zeiger vom Typ NSString erstellen
string1 = [NSString alloc]; // Speicher für den NSString reservieren
[string1 init];             // Das Objekt initialisieren

Im Normalfall schreibt man alles in eine Zeile und verschachtelt alloc und init. Folgende Zeile hat also den gleichen Effekt:
NSString* string1 = [[NSString alloc] init];

Hierfür gibt es zusätzlich nochmal eine Kurzform, die den gleichen Effekt hat:
NSString* string1 = [NSString new];

Nun macht die obige Zeile im Fall von NSString wenig Sinn. Man kann NSString nämlich nach dem init nicht mehr verändern, also keine Zeichenkette mehr zuweisen (im Gegensatz zur Klasse NSMutableString). Daher haben solche Objekte erweiterte init-Methoden, die eine zusätzliche Parameter-Übergabe erlauben, um zum Beispiel einen NSString mit einem Wert zu befüllen. Alle init-Methoden finden sich zu jeder Klasse in der Dokumentation in Xcode. Hier ein Beispiel:
NSString* string1 = [[NSString alloc] initWithString:@"Hallo Welt!"];

Hier wird ein String mit einem anderen String (@"Hallo Welt") befüllt. Nochmal zur Erinnerung: Das ist nur ein Beispielkonstrukt. Das @-Zeichen vor einem doppelten Anführungszeichen ist ein sogenanntes Makro; also eine Kurzform zur Erstellung eines Strings. Da man String-Objekte sehr häufig benötigt, wurde dieses Makro eingeführt. Letztendlich erzeugt das @-Zeichen bereits einen NSString. Davon wird oben dann ein weiterer String erstellt, also eine Kopie.

Wenn man den String nicht mehr benötigt, dann sollte man nicht vergessen den Speicher wieder freizugeben:
[string1 release];

Das Retain- und Release Konzept von Cocoa ist ein eigenes, großes Thema. Wir haben das in unserem Videocast, beginnend mit der Folge '#007: Speicher, Zeiger und Objekte' thematisiert. Zusätzlich gibt es den Grundlagenartikel Retain, Release und Autorelease.
5 Kommentare | Permalink | Trackback-Info


Operatoren

Operatoren
Im Grundlagenbeitrag Datentypen haben wir schon einige Operatoren und damit auch Variablen kennengelernt. Operatoren dienen gewöhnlich dazu, verschiedene Bestandteile eines Programms zu verknüpfen, um z.B. einer Variablen etwas zuzuweisen, etwas zu berechnen oder auf etwas zuzugreifen.

Zuweisungs- und Rechenoperatoren (Arithmetische Operatoren)

Einer der häufigsten Operatoren dürfte der Zuweisungsoperator sein, das Gleicheitszeichen. Links vom Gleichheitszeichen steht immer eine Variable. Der Variablen versucht der Computer das zuzuweisen, was rechts vom Gleichheitszeichen steht:
x  = 1;     // Die Variable x erhaelt den Wert 1
y  = x + 1; // Die Variable y erhaelt den Wert 2 nach Addition
z  = y * 2; // Die Variable z erhaelt den Wert 4 nach Multiplikation
z2 = (y+1) / 3; // Die Variable z2 erhaelt den Wert 1 nach Addition und Division

Hier haben wir jetzt die vier Grundrechenarten kennengelernt. Sie funktionieren genau wie in der Mathematik. Da auch beim Computer Punkt- vor Strichrechnung geht, habe ich im letzten Beispiel das (y-1) geklammert, damit erst 1 subtrahiert wird und das Ergebnis dieser Subtraktion durch 3 dividiert wird. Letztendlich haben sogar alle Operatoren eine festgelegte Reihenfolge, in der sie abgearbeitet werden. So wird ja z.B. die Zuweisung immer erst durchgeführt, wenn alles rechts von ihr berechnet wurde.
Zusätzlich gibt es noch den Operator %, der den Rest einer Division bestimmt. 5 % 2 ist also 1.

In einem richtigen Programm muss man alle zu benutzenden Variablen natürlich vorher definieren, wie unter Datentypen beschrieben!

Vergleichsoperatoren

Manchmal muss man in Erfahrung bringen, ob zwei Werte gleich sind, oder vielleicht auch größer oder kleiner. Hier kommen die Vergleichsoperatoren ins Spiel. Das Ergebnis ist für den Computer immer wahr oder falsch, auf englisch true oder false. Für den Computer letztendlich bloß 1 oder 0.
Hier seht ihr nun auch das erste Mal Operatoren, die aus mehr als einem Zeichen bestehen. Nehmen wir mal an, die Variablen von oben haben noch die dort berechneten Werte, dann weisen wir das Vergleichsergebnis nun jeweils der Variablen e zu. Zuerst wird dabei immer der Vergleich rechts vom = durchgeführt und anschließend e entweder 1 (also wahr) oder 0 (also falsch) zugewiesen.
e = x < y;   // ist x kleiner y? Ergebnis ist 1, also wahr
e = x > z;   // ist x groesser z? Ergebnis ist 0, also falsch
e = x >= z2; // ist x groesser oder gleich z2? Ergebnis ist 1
e = x <= z2; // ist x kleiner oder gleich z2? Ergebnis ist 1
e = x != z2; // ist x ungleich z2? Ergebnis ist 0
e = x == z2; // ist x gleich z2? Ergebnis ist 1

Achtung! Der letzte Vergleich beinhaltet eine Falle, über die Anfänger gerne mal stolpern! Es gibt die beiden Operatoren = und ==! Der Erste, das =, ist immer eine Zuweisung, der Zweite, das ==, immer ein Vergleich! Vertut man sich hier, dann kann es zumindest für Anfänger zu überraschenden Ergebnissen kommen, da auch eine Zuweisung ein Ergebnis hat, dass sich mit true oder false umschreiben lässt... dazu aber an anderer Stelle mehr.

Es gibt noch einen großen Haufen weiterer Operatoren. Diese werden in in zusätzlichen Postings besprochen. Weiter gehts daher im Beitrag Operatoren, Teil 2!
3 Kommentare | Permalink | Trackback-Info


Datentypen

Datentypen
Zum Ablegen von Zahlen, Zeichen und jeder anderen Datenstruktur benötigt ein Computer Arbeitsspeicher sowie ein brauchbares, immer gleiches System, wie man Daten in den Speicher schreibt und wieder herausliest. Letztendlich wird ja alles zu 1001110101, aber damit wollen wir als Programmierer möglichst wenig zu tun haben.
Programmiersprachen kennen daher sogenannte Datentypen und Standarddatentypen. Diese werden für uns Programmierer als Variablen dargestellt, genau wie in der Mathematik. Und genau wie in der Mathematik muss man in C-Dialekten auch einmal festlegen ("definieren"), ob eine Variable nun eine reelle Zahl, eine Ganzzahl, eine Zeichenkette oder was immer beinhaltet. Dann weiß der Computer auch, wie er die Nullen und Einsen zu interpretieren hat.

Sowas sieht in einem Computerprogramm so aus:
// Eine Datentypdefinition beginnt immer mit einem Schluesselwort.
// Dieses Schluesselwort stellt den Datentyp der Variable dar.
// Es folgt ein Variablenname, abgeschlossen durch ein Semikolon.

// Mit einem "//" leitet man einen Kommentar wie diesen hier ein.
// Kommentare werden vom Computer komplett ignoriert.

int x; // "int" bedeutet Integer, also "Ganzzahl", x ist der Variablenname
int meinezahl; // Hier heisst die Variable "meinezahl"
float y; // "float" bedeutet Fliesskommazahl, y ist der Variablenname

Durch diese Befehle reserviert der Computer also genug RAM, um die jeweiligen Zahlentypen zu speichern. Ab sofort kann man dort Werte hineinschreiben oder damit rechnen:
x = 1; // Der Variablen x wir der Wert 1 zugewiesen
meinezahl = x + 41; // meinezahl hat nach diesem Befehl den Wert 42
x = x + 1; // x ist nun um eins groesser, als vorher

Wie man hier sieht, wird jeder in sich geschlossene Befehl mit einem Semikolon abgeschlossen. Das gehört zur sogenannten Syntax, der Grammatik einer Programmiersprache. So wie wir einen Punkt hinter einen Satz machen, so benötigt man in der Programmiersprache "C" ein Semikolon hinter einem Befehl, damit der Computer ihn versteht.
Zur Syntax gehört auch, dass man sich nicht beliebige Variablennamen ausdenken darf. Variablen müssen immer mit a-z, A-Z oder einem Unterstrich beginnen, dürfen dann jedoch zusätzlich Zahlen enthalten, aber keine Leerzeichen oder Rechenoperatoren. Selbstverständlich dürfen Variablen auch nicht identisch sein mit bereits vorhandenen Schlüsselwörtern.

Es gibt noch viel mehr Datentypen in C. Diese werdet ihr aber erst nach und nach kennenlernen. Gleiches gilt für die hier schon teilweise benutzten Operatoren. Es ist jetzt erstmal viel spannender, wie man etwas auf dem Bildschirm ausgibt und wo man solche Befehle überhaupt hinschreibt. Schließlich hat man nichts davon, wenn der Computer superschnell rechnet, aber wir das Ergebnis gar nicht sehen können.
Trackbacks:
http://www.wifi-forum.com/wf/member.php?u=20299
http://www.mac-talk.eu/vorstellungen/173-thoor.html
2 Kommentare | Permalink | Trackback-Info