Mit seiner über 20-jährigen Geschichte gehört Objective-C zwar zu den alten Hasen im Programmiergeschäft. Doch im Gespann mit Gnustep schafft es noch immer neue GUI-Anwendungen.
Bei großen Softwareprojekten hilft der objektorientierte Ansatz umfangreiche Mengen Code sinnvoll zu unterteilen. Zum Programmieren grafischer Schnittstellen eignet sich die Technik besonders gut, daher kommt kaum ein Fenstersystem ohne sie aus. Stellt sich nur noch die Frage nach der passenden Programmiersprache. Heute sind das oft Java, das so mancher Programmierer immer noch zu langsam findet, oder C++, das vielen zu komplex ist. Objective-C [1] ist eine Alternative zu den beiden Stars, die sich in vielen Projekten bewährt hat, etwa bei Open Groupware [2].
Objective-C erweitert C um eine objektorientierte Syntax, ist gleichzeitig aber weniger komplex als C++. Syntaktisch orientiert es sich an Smalltalk in sei-ner Schreibweise von Objekten als Nachrichtenempfänger: »[ Objekt Nachricht]«. Objective-C bietet ein dynamisches Typsystem, kann also zur Laufzeit auf Objekte unterschiedlichen Typs passend reagieren.
Die Entstehung
Die Wurzeln von Objective-C liegen in den Next-Computern und ihrem Betriebssystem Nextstep, das sich dieser Sprache bediente [3]. Daraus wurde die freie API-Spezifikation Openstep, die wiederum als Basis für Mac OS X diente, als Apple die Rechte an Nextstep erwarb. So ist die am weitesten verbreitete Progammiersprache für den Mac immer noch Objective-C, obwohl aktuelle Versionen des Apple-GCC auch die Mischung mit C++ erlauben. Die als Objective-C++ bekannte Sprachvariante wird in Version 4.1 der GCC-Mainline erwartet.
Ein Linux-System implementiert Objective-C zweistufig: einerseits als GCC-Compiler-Komponente, die objektorientierte Konstrukte in gewöhnlichen C-Code übersetzt (zumindest theoretisch, praktisch benutzt der GNU-Compiler keine C-Zwischenstufe), andererseits als Runtime-System, das Nachrichten zustellt und sich um die Speicherverwaltung kümmert. In Objective-C geschriebene Programme müssen mindestens den Header »objc/Object.h« einbinden und mit »-lobjc« linken. Mehr darüber, wie man Objective-C-Programme kompiliert, verrät der Kasten “Übersetzen”.
|
Übersetzen |
|---|
|
Wer mit Objective-C programmieren möchte, braucht natürlich einen Compiler, der die Sprache beherrscht. Der GNU-Compiler kann das schon lange, die Sprachunterstützung wird sogar immer noch ausgebaut und verbessert. Das passende Installationspaket findet sich in praktisch jeder Distribution unter dem Namen »gcc-objc« oder einem ähnlichen. Die zugehörige Library befindet sich häufig in einem extra Paket »libobjc«. Die Übersetzung gestaltet sich ähnlich wie bei C, die Objective-C-Quelldateien tragen aber per Konvention die Endung ».m«: cc -c Ellipse.m Circle.m Geo.m cc -o Geo Geo.o Circle.o Ellipse.o -lobjc Diese beiden Zeilen erzeugen aus ihnen zunächst Objektdateien und linken sie und die Libobjc anschließend zu einem ausführbaren Programm namens »Geo«. |
Headerdateien enthalten üblicherweise die Definition der Klassen. Das Schlüsselwort dafür lautet in Objective-C »@interface«. Danach folgen der Name der Klasse, die Elternklasse und eventuell weitere Modifikatoren, die die Vererbung bestimmen. Die Deklaration endet mit »@end«. Eine Klasse »MyObject«, die von »Object« abgeleitet ist, beginnt so:
@interface MyObject: Object
Dann folgen in geschweiften Klammern die Instanzvariablen. Gibt es keine, bleibt dieser Teil einfach leer. Schließlich folgen die Methoden, einmal die mit einem »+« gekennzeichneten Klassenmethoden, zum zweiten die mit »-« markierten Instanzmethoden. Die Typen der Rückgabewerte stehen in Klammern vor dem Methodennamen, dahinter die optionalen Argumente, mit Schlüsselwörtern und Doppelpunkten getrennt:
+ (id) init; - (void) setRadius: (int) r; - (void) draw;
Der Typ »id« kann in Objective-C beliebige Objekte aufnehmen, unterstützt also das dynamische Typsystem.
Nachrichten verschicken
Das Hauptprogramm ruft Methoden des Objekts über den erwähnten Message-Mechanismus auf, wobei es für Argumente die Notation mit den Doppelpunkten benutzt: »[circle setRadius: 3]«. Listing 1 zeigt einen kompletten Header mit den beiden Instanzvariablen »r1« und »r2« sowie der Klassenmethode »init« und mehreren Instanzmethoden.
|
Listing 1: |
|---|
01 #import <objc/Object.h>
02
03 @interface Ellipse: Object
04 {
05 float r1, r2;
06 }
07 + (id) init;
08 - (void) setR1: (float)r;
09 - (void) setR2: (float)r;
10 - (void) setAchse1: (float)a Achse2: (float)b;
11 - (void) printDim;
12 - (float) area;
13 - (float) r1;
14 - (float) r2;
15 @end
|
Um von der Klassendeklaration Gebrauch zu machen, bindet der Programmierer den Header in seine anderen Dateien ein. Dazu kann er wie in C »#include« benutzen, muss allerdings mit den bekannten »#ifndef«-Konstrukten verhindern, dass Header mehrfach eingebunden werden. Objective-C bietet stattdessen »#import«, das genau dies erledigt, also einiges an Schreibarbeit spart. Listing 1 bindet so den Haupt-Header von Objective-C ein, der zum Beispiel die Definition von »Object« enthält, von dem »Ellipse« grundlegende Eigenschaften erbt.
Implementation
In größeren Projekten bietet es sich an, die Implementierung der Methoden in eine andere Datei auszulagern, auch wenn das nicht obligatorisch ist. Sie beginnt in jedem Fall mit dem Schlüsselwort »@implementation« und erstreckt sich bis zum »@end«. Die Elternklasse braucht jetzt nicht mehr hinter dem Klassennamen zu stehen, der Compiler kennt sie ja bereits aus der Deklaration. Listing 2 implementiert die in Listing 1 deklarierte Klasse.
|
Listing 2: |
|---|
01 #import <stdio.h>
02 #import <math.h>
03 #import "Ellipse.h"
04
05 @implementation Ellipse
06 + (id) init
07 {
08 return [super init];
09 }
10 - (void) setR1: (float) r
11 {
12 r1 = r;
13 }
14 - (void) setR2: (float) r
15 {
16 r2 = r;
17 }
18 - (void) setAchse1: (float)a Achse2: (float)b;
19 {
20 r1 = a;
21 r2 = b;
22 }
23 - (void) printDim
24 {
25 printf("Ellipse (%f, %f)n", r1, r2);
26 }
27 - (float) area
28 {
29 return r1 * r2 * M_PI;
30 }
31 ...
32 @end
|
Die Methode »init« hat hier wenig zu tun. Sie initialisiert nur die Elternklasse, indem sie ihr ebenfalls eine Nachricht »init« schickt. Auf die Elternklasse greifen Klassen und Objekte über »super« zu. Die beiden Methoden »setR1« und »setR2« setzen jeweils einen Scheitel der Ellipse, »setAchse1« macht dies in einem Zuge. Objective-C-Programmierer lassen übrigens normalerweise die Argumentvariablen weg, wenn sie sich über Methoden unterhalten, und schreiben nur die Schlüsselwörter mit Doppelpunkten, also »setAchse1:Achse2:«.
Die beiden letzten Methoden in Listing 2 veranschaulichen zwei unterschiedliche Anwendungsfälle: »printDim« gibt über die C-Bibliotheksfunktion »printf()« die beiden Scheitelwerte direkt aus, während »area« den Betrag der Fläche einfach zurückgibt. Listing 3 enthält ein kurzes Hauptprogramm, das veranschaulicht, wie die Klasse zu benutzen ist. Zeile 5 deklariert im C-Stil eine Zeigervariable (einen Pointer) vom Typ Ellipse, die auf das erzeugte Objekt zeigen soll. Die folgende Zeile reserviert erst mit der Nachricht »alloc« Speicher für das Objekt und schickt ihm dann mit »init« die Nachricht zur Initialisierung, meist der Instanzvariablen.
|
Listing 3: |
|---|
01 #import "Ellipse.h"
02
03 int main(int argc, char **argv)
04 {
05 Ellipse *ellipse;
06
07 ellipse = [[Ellipse alloc] init];
08 [ellipse setR1: 3.3];
09 [ellipse setR2: 1.5];
10 [ellipse printDim];
11 printf("Fläche: %fn", [ellipse area]);
12 }
|
Dieser Nachrichtenfluss lässt sich wie im Beispiel schachteln, weil »alloc« ein Objekt zurückgibt, das die Message empfängt. Die Zeilen 8 und 9 legen die Werte der beiden Ellipsenradien fest. Die Methode »printDim« gibt wie beschrieben die beiden Werte wieder aus. Das folgende Printf bezieht den Float-Wert von der Objekt-Methode »area«.
Interessant wird objektorientierte Programmierung unter anderem dadurch, dass geschickte Vererbung hilft weniger Code zu schreiben. Das spart von Anfang an Zeit und verringert den Aufwand zur Pflege großer Programme. Die Ellipse-Klasse profitiert bereits davon, indem sie vom Objective-C-Basisobjekt grundlegende Eigenschaften erbt, beispielsweise die Vererbbarkeit selbst, die Speicherverwaltung und einiges mehr. Sie selbst führt einige Variablen ein, die geometrische Eigenschaften festlegen, und Methoden für Ein- und Ausgabe.
Ableiten
Aus objektorientierter Sicht kann man einen Kreis als Spezialisierung einer Ellipse verstehen, deren zwei Radien den gleichen Wert besitzen. Es bietet sich also an, die zugehörige Klasse von der Ellipse-Klasse abzuleiten und von den implementierten Methoden Gebrauch zu machen. Die Klasse »Circle« führt der Klarheit wegen eine eigene Instanzvariable »radius« ein – nur mit einem der beiden Ellipsenradien zu arbeiten wäre theoretisch auch möglich. Das dazu gehörige Header-File ist hier nicht abgedruckt, aber auf der Website des Linux-Magazins [4] zu finden.
Listing 4 zeigt die Implementierung der Klasse »Circle«. Erklärenswert ist dabei höchstens die Zuweisung in Zeile 7: Weil Circle eine eigene Instanzvariable »radius« einführt, funktioniert die »area«-Methode zur Berechnung der Fläche der Elternklasse »Ellipse« nicht automatisch. Das klappt erst, wenn die beiden Ellipsenradien »r1« und »r2« denselben Wert haben wie »radius«.
|
Listing 4: |
|---|
01 #import <stdio.h>
02 #import "Circle.h"
03
04 @implementation Circle
05 - (void) setRadius: (float) r
06 {
07 r1 = r2 = radius = r;
08 }
09 - (void) printDim
10 {
11 printf("Circle (%f)n", radius);
12 }
13 @end
|
Dank Vererbung funktioniert die Methode »area« dann auch mit Circle-Objekten, obwohl sie nicht eigens implementiert wurde. Der Code-Abschnitt im Hauptprogramm sieht so aus:
[circle setRadius: 2.0];
printf("Fläche: %fn", [circle area]);
In diesem primitiven Mechanismus erschöpft sich das Repertoire objektorientierter Techniken natürlich nicht. Die meisten von Java und C++ bekannten Features gibt es in Objective-C schon lange, wenn auch unter anderen Namen.
Neuer Aspekt
Wie bereits erwähnt beginnt eine Klassendefinition in Objective-C mit »@interface«. Eine Schnittstellendefinition für Objekte heißt daher nicht wie in Java Interface, sondern Protokoll. Das zugehörige Schlüsselwort lautet »@protocol«. Ansonsten führt die Protokolldefinition ebenso wie die Deklaration einer Klasse die zulässigen Methoden auf. Hinter der Klasse, die einem Protokoll gehorcht, steht der Protokollname in spitzen Klammern:
@interface Circle: <GeoProtocol>
Objective-C kann existierende Klassen nachträglich um Methoden erweitern, ohne von ihnen erst eine neue Klasse abzuleiten. Die Welt modischer Programmierparadigmen nennt so etwas aspektorientiert, in Objective-C heißt das entsprechende Konstrukt Kategorie (Category). Soll beispielsweise die Klasse »Circle« noch um Zeichenfunktionen erweitert werden, bietet sich eine Category »Drawing« an, die alle nötigen Methoden implementiert. Die Datei »Drawing.h« deklariert die neue Methoden der Klasse »Circle«, »Drawing.m« implementiert sie. Der Kategoriename stehen in runden Klammern hinter dem Klassennamen:
@interface Circle (Drawing)
Die Anwendung von Kategorien beschränkt sich allerdings auf Methoden. Neue Variablen lassen sich so nicht zur Klasse hinzufügen.
Die Fehlersuche in Objective-C-Programmen hat sich stark vereinfacht, seit der GNU-Debugger ab Version 6 auch Objekte kennt und die passende Syntax beherrscht. Nun lassen sich beispielsweise Breakpoints in Objektmethoden über »b -[Objekt Methode]« festlegen. Vor Klassenmethoden steht analog zu ihrer Definition ein»+«.
Schöne Umgebung
Jede objektorientierte Sprache ist nur so gut wie ihre Klassenbibliothek. Auch Java wurde nur populär dank ihrer umfangreichen Klassenbibliotheken, die praktisch jeden Anwendungsbereich abdecken. Apple, das auch auf Objective-C setzt, bietet mit Cocoa ein umfangreiches Framework (das ist übrigens auch der offizielle Name für Openstep-Bibliotheken) für die Entwicklung moderner GUI-Anwendungen. Cocoa hält von einfachen Stringfunktionen über GUI-Objekte bis zu OpenGL-, Sound- und Video-Klassen alles dafür Nötige bereit.
Unter Linux sieht es für Objective-C-Entwickler nicht so rosig aus, die aktuellen Desktops KDE und Gnome haben damit nichts am Hut. Es gibt nur noch ein altes Objective-C-Binding für GTK 1.2. Doch besteht eine Gruppe von Entwicklern, die wie Apple aus der Vergangenheit schöpfen: Das Gnustep-Projekt arbeitet schon seit vielen Jahren an einer freien Implementierung des Openstep-API (Abbildung 1) und hat dabei einiges erreicht. Programme, die auf spezielle Cocoa-Funktionen verzichten, sind zwischen Mac und Gnustep portabel. Beispiele dafür sind der Mailclient Gnumail.app [5] oder der freie Buchleser Digibux der digitalen Bibliothek [6].

Abbildung 1: Gnustep bietet ein umfangreiches API für die Anwendungsentwicklung, von grundlegenden Hilfsbibliotheken bis zum GUI.
Für einige Distributionen gibt es Binärpakete, Gnustep aus dem Quellcode zu kompilieren ist normalerweise aber auch kein Problem. Wichtig ist nur die richtige Reihenfolge beim Übersetzen der Einzelpakete »gnustep-make«, »gnustep-base«, »gnustep-gui« und »gnustep-back« [7]. Alternativ bietet sich für den schnellen Einstieg das Paket »gnustep-startup« an, das diese Frameworks enthält und sich mit einem Befehl übersetzen und installieren lässt [8].
Nach der Installation erwarten Gnustep-Anwendungen eine Reihe von Umgebungsvariablen wie »GNUSTEP_ROOT« oder »GNUSTEP_PATHLIST«. Auch die Programmierung von Gnustep-Software baut auf solche Variablen, zum Beispiel »GNUSTEP_MAKEFILES«. Alle diese Variablen setzt ein Shellskript, das der Source-Befehl einliest:
source /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
Wer den Quellcode einer Anwendung wie Gnumail.app heruntergeladen und entpackt hat, kann einfach über »make« die Übersetzung starten. Normalerweise entfällt ein von vielen C-Programmen bekannter Configure-Schritt, denn Gnustep bietet auf allen Plattformen eine definierte Umgebung [9].
Für den Programmierer vereinfachen sich dadurch die Makefiles, die bei Gnustep meist »GNUmakefile« heißen. Zum Beispiel genügt es zum Kompilieren einer grafischen Anwendung, die Make-Makrodateien »$(GNUSTEP_MAKEFILES)/application.make« sowie »common.make« einzubinden und die Quelldateien aufzuführen. Um auf der Kommandozeile von Hand zu übersetzen, muss der Programmierer die Include- und Link-Pfade angeben, beispielsweise:
cc -o app app.m -I/usr/GNUstep/System/Library/Headers -L /usr/GNUstep/System/Library/Libraries -lgnustep-gui -lobjc
Das entstehende Binary »app« lässt sich ohne weiteres ausführen.
Extra Starter
Richtige Gnustep-Anwendungen bestehen aus mehr als einer Objektdatei, sie enthalten zusätzliche Ressourcendateien. Daher ist eine solche Applikation auch keine einfache Datei, sondern ein Verzeichnis mit der Endung ».app«, in dem sich alle nötigen Dateien befinden – übrigens genauso wie beim Mac. Den Programmstart übernehmen deshalb die Wrapper-Programme »openapp« beziehungsweise »gopen«. So startet der Anwender das Mailprogramm mit »gopen GNUmail.app«. Um Gnustep-Anwendungen zu debuggen, benutzt man statt »openapp« den Anwendungsstarter »debugapp«, der den Debugger aufruft.
Gnustep-GUIs
Gnustep bringt ein eigenes Root-Objekt namens »NSObject« mit, das statt des sonst üblichen »Object« als Grundlage aller abgeleiteten Klassen dient. Dafür müssen die Quelldateien die nötigen Header »AppKit/AppKit.h« einbinden. Um eine grafische Anwendung aufzubauen, bietet Gnustep Klassen wie »NSApplication«, »NSWindow« oder »NSButton«. Damit der Programmierer nicht jede Button-Aktion selbst programmieren muss, hilft beim GUI-Bauen das grafische Tool Gorm (Abbildung 2), ein Klon von Nexts Interface Builder. Das Tool Projectbuilder managt Softwareprojekte auf höherer Ebene und ruft fürs GUI-Design wiederum Gorm auf.

Abbildung 2: Der GUI-Designer Gorm hilft dem Programmierer, Gnustep-Anwendungen grafisch aufzubauen.
Ähnlich wie Glade für GTK erzeugt das Gnustep-Programm Renaissance GUI-Beschreibungen in XML. Ein Laufzeitsystem interpretiert diese Beschreibungen und präsentiert damit eine funktionierende grafische Oberfläche. Renaissance ist für echte Kompatibilität zu Apples Cocoa ausgelegt, Programme laufen tatsächlich auf beiden Plattformen.
Was fehlt
Mit seiner vergleichsweise einfachen Syntax eignet sich Objective-C gut für moderne GUI-Anwendungen, vor allem im Zusammenspiel mit dem Gnustep-Framework. Doch fristet Gnustep unter Linux ein Schattendasein, denn mit Gnome und KDE hat es nicht viel zu tun. Nur optisch wirkt es etwas altbacken, der Theme-Support, der dies ändern könnte, ist noch in der Entwicklung ([10], in Abbildung 2 zu sehen).
Andererseits existieren zu den GUI-Toolkits der großen Desktops (Qt und GTK) keine aktuellen Sprachbindungen für Objective-C. Nur für GTK 1.2 gibt es noch eine ältere Bibliothek. Dabei würde sich die Sprache gerade für GTK eignen, dessen C-Schnittstelle gewöhnungsbedürftig ist. Wer hier in die Bresche springt und ein Objective-C-Interface für GTK 2 schreibt, kann der Community einen guten Dienst erweisen.
|
Infos |
|---|
|
[1] Objective-C: [http://www.objc.info/about] [2] Open Groupware: [http://www.opengroupware.org] [3] Cocoa-Geschichte: [http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaOverview/Articles/CocoaHistory.html] [4] Listings: [https://www.linux-magazin.de/Service/Listings/2006/02/Objective-C] [5] Gnumail.app: [http://www.collaboration-world.com/cgi-bin/project/index.cgi?pid=2] [6] Digibux: [http://wiki.directmedia.de/index.php/Digibib/Linux] [7] Gnustep-Howto: [http://www.gnustep.org/resources/documentation/User/GNUstep/gnustep-howto_3.html] [8] Gnustep-Startup: [http://www.gnustep.org/experience/Startup.html] [9] Gnustep-Datei-Hierarchie: [http://www.gnustep.org/resources/documentation/User/GNUstep/filesystem.html] [10] Camaelon Theme Engine: [http://dromasoftware.com/etoile/mediawiki/index.php?title=Camaelon] |





