| Dieser Artikel oder Abschnitt bedarf einer Überarbeitung. Näheres ist auf der Diskussionsseite angegeben. Hilf bitte mit, ihn zu verbessern, und entferne anschließend diese Markierung. |
Objective-C, auch kurz ObjC genannt, erweitert die Programmiersprache C um Sprachmittel zur objektorientierten Programmierung. Objective-C++ erlaubt die teilweise Mischung von Objective-C mit C++-Code mit dem Ziel, älteren Code verwenden zu können. Objective-C ist die primäre Sprache von Cocoa (Mac OS X) und GNUstep.
Die Syntax und Konzeption der objektorientierten Erweiterungen ist an Smalltalk angelehnt und von der gewöhnlichen prozeduralen C-Syntax strikt getrennt. Diese Trennung erlaubt es, das gleiche Konzept zur Erweiterung auf andere imperative Sprachen anzuwenden; so gibt es z.B. ebenfalls Objective Pascal.
Inhaltsverzeichnis |
Objective-C wurde hauptsächlich von Brad Cox in den 80er Jahren bei PPI, später Stepstone, entwickelt, später dann von NeXT in die GNU Compiler Collection integriert, um als Basis für NextStep zu dienen.
Einer der Design-Gedanken von Objective-C war es, die Flexibilität von Smalltalk anzunähern, jedoch auf das zu verzichten, was das Laufzeitverhalten verschlechtern könnte. Der offensichtlichste Verzicht gegenüber Smalltalk ist das Fehlen von Blöcken. Daher ist ein Objective-C-Programm bereits zur Übersetzungszeit vollständig compilierbar.
Viele Konzepte sind gar nicht in der Sprachdefinition selbst festgelegt, sondern werden erst durch das Framework, also etwa Cocoa oder GNUStep ermöglicht. Insbesondere ist das gesamte Laufzeit-System nicht im Compiler implementiert, sondern besteht aus C-Funktionen. Bei Aufruf einer Member-Funktion in C++ fügt etwa grundsätzlich der Compiler den entsprechenden Code ein, während in Objective-C (bei Versenden einer Nachricht an ein Objekt, dazu später mehr) die C-Funktion objc_msg_send() aufgerufen wird. Daher ist eine Darstellung ohne das entsprechende Laufzeitsystem kaum denkbar und nicht sinnvoll. Orginäre Objective-C-Schlüsselwörter erkennt man indessen an dem vorangestellten @.
Die wohl gerade gegenüber C++-Programmierern bemerkenswerteste Eigenschaft ist das späte Binden von Methoden. Polymorphie ist im Gegensatz zu Sprachen, die auf Simula-67 basieren nicht nur innerhalb einer Klassenhierarchie möglich, sondern auch darüber hinaus. Eine Methode mit einem bestimmten Namen (Signatur) kann von Objekten jeder Klasse ausgeführt werden, die sie implementieren. Es ist nicht erforderlich, dass der Aufrufer die Klasse kennt oder die Methoden bereits in einer Basisklasse – wenn auch nur virtuell – definiert worden sind.
@interface KlasseA : NSObject {
// Instanzvariablen
…
}
- (void)doSomething; // Eine neue Methode in der Subklasse
- (void)dumpToLog; // Noch eine
@end
@interface KlasseB : NSObject {
// Instanzvariablen
…
}
- (void)dumpToLog; // Es wird nur die zweite Methode implementiert.
@end
id anObject = … // irgendeine Instanz der Klasse A oder B. [anObject dumpToLog]; // Obwohl beide Implementierungen unabhängig sind und // keine entsprechende Methode in der Basislasse NSObject // existiert, wird die Methode jeweils sicher gefunden
Es ist daher für den Absender nicht notwendig, die Klasse des Empfängers zu kennen. Vielmehr existiert wie im obigen Code erkennbar ein Typ id, der für jedes Objekt jeder Klasse stehen kann.
Dies bedingt in Objective-C die strikte Trennung von Nachrichten und Methoden. Man spricht daher eigentlich in Objective-C gar nicht von Methodenaufrufen. Vielmehr gibt es einen Nachrichtensender (Sender) und einen Nachrichtenempfänger (Receiver). Alleine der Receiver entscheidet anhand der Nachricht, welche Methode ausgeführt wird. Dabei wird zunächst versucht, eine gleichnamige Methode zu finden. Es existiert dazu korrespondierend ein Datentyp SEL.
SEL nachricht = @selector( dumpToLog ); id anObject = … [anObject performSelector:dumpToLog];
Es ist darüber hinaus möglich, Nachrichtennamen erst zur Laufzeit zu erstellen.
// Ein Text, der den Namen einer 1:n-Beziehung enthält. NSString* relationship = @"children"; // Mittels String-Verarbeitung bauen wir uns zur Laufzeit daraus einen Nachrichtentext NSString* messageText = [NSString stringWithFormat:@"countOf%@", relationship]; // Das wird jetzt in eine Nachricht umgewandelt SEL message = NSSelectorFromString( messageText ); // Der Empfänger führt die entsprechende Methode aus: int countOfInstancesInRelationship = [receiver performSelector:message];
Kompliziertere Nachrichten, insbesondere mit zahlreichen Argumenten, werden als Instanzen der Klasse NSNotification abgebildet.
Hieraus ergibt sich die Möglichkeit, in einer IDE ganze Objektgraphen und Bedienungsoberflächen zu gestalten und zu verbinden, ohne dass die Eingabe von Sourcecode durch den Programmierer oder durch die IDE selbst erforderlich ist. Umgekehrt handelt es sich aber um das "normale" Vorgehen des Dispatchers, sodass kein Aufsatz hierfür erforderlich ist und der Programmierer jederzeit die Kontrolle über die Ausführung jeder Methode behält (Beispielvideo von Apple). In der Realität entfällt damit ein Großteil der Programmierarbeit.
Ähnlich wie in Java mit Interfaces lassen sich in Objective-C Sätze von Methoden in Protokollen zusammenfassen:
@protocol MyDelegateProtocol
// Instanzen der Klasse FileSystemGuard verschicken diese Nachricht an Delegates
- (void)fileSystemGuard:(FileSystemGuard*)guard detectedMount:(NSString*)mountPath;
@end
Zur Laufzeit wird zu jedem Objekt ein Verweis auf seinen Typen, also die Klasse mitgeführt. Die Klasse enthält darüber hinaus eine Beschreibung aller Instanzvariablen und implementierten Methoden. Hieran entscheidet der Dispatcher im Receiver, welche Methode er der Nachricht zuordnet. Umgekehrt kann der Absender erfragen, welche Methoden implementiert sind.
// Eine Methode, welche Nachrichten des GUI implementiert
- (IBAction)doSomething:(id)sender {
// Klassenabfrage: RTTI
if( [sender isKindOfClass:[NSButton class]] ) {
// Es handelte sich um eine Instanz der Klasse NSButton
[self doSomethingElseWithInt:[sender tag]];
}
// Methodenabfrage: Reflexion
if( [sender respondsToSelector:@selector( tag)] ) {
…
}
-(void)setDelegate:(id)delegate {
// Nur Delegates akzeptieren, die ein Mindestmaß an Methoden implementieren. Der Methodensatz ist
// im Protokoll MyDelegateProtocol angegeben.
if( ![delegate conformsToProtocol:@protocol( MyDelegateProtocol )] ) {
…
}
}
In Objective-C existieren nicht nur Instanzobjekte (kurz: Instanzen), sondern auch Klassenobjekte. Diese können jedoch keine Member-Variablen enthalten und sind stets Singletons. Klassenmethoden werden durch ein vorangestelltes + gekennzeichnet.
Da es sich um Objekte handelt, können diese zugewiesen werden. Sie haben selbst den Typen class.
// Eine Variable, die eine Klasse speichert class aShapeClass;
…
// Es können Klassenobjekte zugewiesen werden:
- (void)setShapeClass:(class)newShapeClass {
aShapeClass = newShapeClass;
}
- (void)addNewShape {
id shape = [[aShapeClass alloc] init]] // Erzeugung einer Instanz der Klasse Shape
[shape markForRedraw];
[arrayWithAllShapes addObject:shape];
…
}
Bemerkenswert ist in diesem Zusammenhang die Eigenschaft, dass es für diese Objekte einen self-Zeiger (entspricht in C++ dem this-Zeiger) gibt, die der Polymorphie zugänglich sind:
@interface Shape : NSObject {
}
+ (NSRect)boundingRectForShapeRect:(NSRect)shapeRect
@end
@interface Circle : Shape {
}
@end
@imlementation Circle {
// Überschreiben einer Klassenmethode der Basisklasse
+ (NSRect)boundingRectForShapeRect:(NSRect)shapeRect {
// Subklassenmethode
// Es existiert ein self-Zeiger, der auf Circle oder eine Subklasse von Circle zeigt.
[self doSomething];
…
}
… NSRect boundingRect; // Nicht polymorpher Aufruf: Entspricht in etwa Shape::boundingRectForShapeRect() in C++ boundingRect = [Shape boundingRectForShapeRect:aRect]; Shape* aShape = … // Eine Instanz der Klasse Shape oder einer Subklasse wie Circle // Polymorpher Auf ruf einer Klassenmethode [[aShape class] boundingRectForShapeRect:aRect]: …
Die Syntax von Objective-C erweitert die C-Syntax um objektorientierte Elemente. Diese Syntaxerweiterungen lehnen sich jedoch nicht an die C-Syntax an, wie es etwa viele verbreitete objektorientierte Programmiersprachen tun, sondern an die der Programmiersprache Smalltalk.
Auch das objektorientierte Konzept von Objective-C unterscheidet sich von dem anderer Programmiersprachen, die auf Simula zurückgehen. Es entspricht dem Konzept von Smalltalk. Nachrichten sind in Objective-C gleichzeitig Instanzobjekte der Klasse NSInvocation. Anstatt den Methodenaufruf als eine Art an das Objekt gebundene Funktion zu betrachten, ist dieser Mechanismus in Objective-C das Versenden einer Nachricht.
[Objekt Nachricht]
sendet also die Nachricht, methode auszuführen, an das Objekt. Z.B.
stringLaenge = [@"Hallo Welt" length];
Sendet die Nachricht length an das String-Objekt "Hallo Welt", das mit der Länge „antwortet“.
Parameter können jeweils nach einem Doppelpunkt angegeben werden, danach kann der Methodenname weitergehen, zum Beispiel:
[NSColor colorWithCalibratedRed: 0.0 green: 0.0 blue: 0.0 alpha: 0.0];
(Der eigentliche Methodenname wäre dann colorWithCalibratedRed: green: blue: alpha:.)
Nachrichten können auch verschachtelt werden. Z.B.
NSString*string=@"Hallo Welt"; NSData*data=[NSData dataWithBytes:[string cString] length:[string cStringLength]];
Hiermit wird ein neues Objekt der Klasse NSData erstellt. Die Bytes, die in das neue Objekt kopiert werden, werden mit [string cString] erfragt, die Länge des Blocks mit [string cStringLength].
Es werden also die Rückgabewerte der Methoden verwendet. Dies geht auch beim Empfänger, zum Beispiel entspricht die Methode new diesem Aufruf:
[ [Klasse alloc] init]
Das Objekt, das mit [Klasse alloc] erzeugt wurde, bekommt die Botschaft init.
Kann ein Objekt eine Nachricht nicht verstehen, so kann diese mittels Forwarding an ein anderes Objekt weitergeleitet werden.
Die Nachricht selbst ist nicht statisch wie in C++, sondern kann Gegenstand des Programmes sein. Hierzu gibt es zum einen den Datentypen SEL zum anderen kann der Selector zur Laufzeit aus einem String gebildet werden. Hierdurch ist es etwa möglich, textuelle Beschreibungen eines Models zur Laufzeit zu laden und zu verwenden.
Um seine Eigene Art von Objekten zu erstellen, muss man sie in einer Klasse beschreiben. Dazu werden im @interface-Teil – gewöhnlich in einer Header-Datei – der Zustand solcher Objekte und ihre Eigenschaften definiert, d.h. ihre Variablen und Methoden. Hier im Beispiel eine Bruch-Klasse.
@interface Bruch:NSObject {
int nenner; // Zustandsgrößen, auch Attribute genannt
int zaehler;
}
- (void) printLn; // Fähigkeiten
- (void) setNenner: (int)n zaehler: (int)z;
- (int) nenner;
- (int) zaehler;
- (float) float;
- init; // Anfangszustand setzen
@end
Die @implementation beschreibt, wie die Fähigkeiten ausgeführt werden:
@implementation Bruch:NSObject
- (void) printLn {
printf("%i/%i\n",nenner,zaehler);
}
- (void) setNenner: (int) n zaehler: (int) z {
zaehler = z;
nenner = n;
}
- (int) nenner {
return nenner;
}
- (int) zaehler {
return zaehler;
}
- (float) float {
return ((float) nenner)/zaehler;
}
- (id) init {
if (self = [super init]) [self setNenner: 1 zaehler:1];
return self;
}
@end
Ein Objekt kann nun erstellt werden, indem man die Nachricht [Klasse alloc] sendet¹. Die Bruch-Klasse kann man also folgendermaßen benutzen:
int main(int argc, const char **argv)
{
id MeinBruch = [[Bruch alloc] init];
[MeinBruch setNenner: 10 zaehler:20];
[MeinBruch printLn];
printf("%f",[MeinBruch float]);
[MeinBruch release];
return 0;
}
¹ Man sagt auch die Klasse wird „instanziert“, MeinBruch ist eine Instanz von Bruch
Objective-C besitzt so genannte Klassenobjekte. Dies bedeutet: Nicht nur die Instanzen, sondern auch die Klassen sind Objekte und können Nachrichten empfangen, wie oben [Klasse alloc]. – Zum Instanzieren sind damit keine zusätzlichen Sprachelemente wie Konstruktoren und Schlüsselwörter nötig.
In der Klassendefinition werden Klassenmethoden mit ‚+‘, Instanzmethoden mit ‚-‘ gekennzeichnet.
+alloc ist von der Sprachsyntax her gesehen eine von vielen möglichen Klassenmethoden und der eigenen Implementierung zugänglich. Die Methode zu überschreiben ist jedoch nur für Experten eine gute Idee. Ganz im Gegensatz ist die Methode +initialize, die beim Programmstart aufgerufen wird, standardmäßig leer und kann für klassenbezogene Vorauseinstellungen überschrieben werden.
Objective-C fügt zu den Standard-C-Datentypen den Datentyp id hinzu. Ein Objekt des Types id ist irgendein Objekt. Was mit diesem Objekt angefangen werden kann, wird erst zur Laufzeit bestimmt. Hierzu existieren Methoden, etwas über das Objekt zu erfahren. Wichtig sind:
[obj class] // bestimmt die Klasse[obj respondsToSelector:] // bestimmt, ob eine Methode vorhanden ist[obj conformsProtocol:] // bestimmt, ob ein Protokoll implementiert istDas Typkonzept erlaubt zudem die Typisierung des allgemeinen Objektes oder anderer Objekte durch (formale) Protokolle. Diese legen einen Satz von Methoden fest. Auch diese Protokolle müssen nicht in einem Hierarchiebaum einheitlich sein, werden aber vererbt.
Die Typfreiheit erlaubt die Erstellung allgemeiner Container, die anders als templates auch typgemischt sein können.
NSMutableDictionary *meineWelt = [[NSMutableDictionary alloc] init];
// Eine Map mit einem String, einer URL und einem Wert
[meineWelt setObject:@"Ein Text" forKey:@"Text"];
[meineWelt setObject:[NSURL URLWithString:@"www.example.com"] forKey:@"URL"];
[meineWelt setObject:[NSNumber numberWithInt:3] forKey:@"Zahl"];
// Alle Objekte implementiern die Methode description
NSLog( @"%@", [[meineWelt objectForKey:@"Text"] description] );
NSLog( @"%@", [[meineWelt objectForKey:@"URL"] description] );
NSLog( @"%@", [[meineWelt objectForKey:@"Zahl"] description] );
[meineWelt release];
Ausgabe:
Ein Text
www.example.com
3
Objective-C bindet einen Methodenaufruf erst zur Laufzeit an eine Methode. Grundsätzlich ist dies auch in C++ so. Jedoch muss in C++ bereits zur Übersetzungszeit sichergestellt sein, dass eine Methode existiert, weil sie zu der Klassenhierarchie gehört. C++ ist also nur bereit, innerhalb eines Zweiges einer Hierarchie spät zu binden, während Objective-C dies unabhängig von der Stellung einer Klasse in einem Hierarchiebaum macht. Dies soll an einem syntax-neutralen Beispiel verdeutlicht werden:
Klasse A
methode()
Klasse B von Klasse A
methode()
Klasse C
methode()
...
// Spätes Binden in C++
A* objekt = new B;
objekt->methode(); // Ruft in Wahrheit B::methode auf
objekt = new C; // Führt zu Fehlern, da typfremd.
objekt->methode(); // Auch mit casting geht dies nicht, weil methode()
// in einem anderen Zweig des Baumes auftaucht,
// obwohl C::methode() sehr wohl existiert
// Spätes Binden in oC
id objekt = new B; // id bedeutet „Irgend ein Objekt“
objekt->methode(); // Funktioniert, weil B methode() kennt
objekt = new C;
objekt->methode(); // Funktioniert auch, weil C die methode() kennt
// Wo sie in der Klassenhierarchie bekannt ist,
// spielt keinerlei Rolle
Zu diesem Zweck führt Objective-C zur Laufzeit umfangreiche Informationen über ein Objekt mit, was über RTTI hinausgeht.
Aus dem letztlich gleichen Grunde ist es auch ohne weiteres möglich, zur Laufzeit erst eine Methode zu bestimmen. Die Methode kann sich ähnlich wie bei einem Methodenzeiger in einer Variablen befinden, die erst zur Laufzeit mit einem Wert besetzt wird:
SEL methode;
methode = @selector( eineMethode );
[objekt performSelector:methode]; // Ruft die Methode eineMethode auf
methode = @selector( andereMethode );
[objekt performSelector:methode]; // Ruft andereMethode auf
Von dieser Technik macht etwa Cocoa bei der Bindung von Interface-Elementen an Methoden regen Gebrauch. Die Bestimmung der Methode kann auch durch ihren Klarnamen erfolgen.
id *aDictionary = [[NSMutableDictionary alloc] init];
[aDictionary setObject:@"eier" forKey:@"egg"];
[aDictionary setObject:@"auto" forKey:@"car"];
[aDictionary setObject:@"Hallo Welt!" forKey:@"Hello, world!"];
NSLog([aDictionary objectForKey:@"Hello, world!"]);
[aDictionary release];
| Dieser Artikel oder Abschnitt bedarf einer Überarbeitung. Näheres ist auf der Diskussionsseite angegeben. Hilf bitte mit, ihn zu verbessern, und entferne anschließend diese Markierung. |