Objective-C ist eine einfache Computersprache, die entwickelt wurde, um echte objektorientierte Programmierung zu unterstützen.
Objective-C erweitert die Standard-ANSI-C-Sprache durch Syntax für Klassendefinitionen, Methoden und Eigenschaften sowie weitere Konstrukte, die die dynamische Erweiterbarkeit von Klassen verbessern. Die Syntax und das Design von Klassen basieren hauptsächlich auf Smalltalk, einer der frühesten objektorientierten Programmiersprachen.
Wenn Sie zuvor andere objektorientierte Programmiersprachen verwendet haben, können Ihnen die folgenden Informationen helfen, die grundlegende Syntax von Objective-C zu erlernen. Viele traditionelle objektorientierte Konzepte wie Kapselung, Vererbung und Polymorphismus spiegeln sich in Objective-C wider. Es gibt einige wichtige Unterschiede, aber diese Unterschiede werden in diesem Artikel erläutert, und bei Bedarf sind detailliertere Informationen verfügbar.
Wenn Sie noch nie in einer Programmiersprache programmiert haben, sollten Sie vor dem Start zumindest ein grundlegendes Verständnis der damit verbundenen Konzepte haben. Die Verwendung von Objekten und die objektorientierte Architektur sind die Grundlage der iPhone-Programmierung; zu verstehen, wie sie interagieren, ist sehr wichtig für die Erstellung Ihrer Programme. Um mehr über objektorientierte Konzepte zu erfahren, lesen Sie bitte Objektorientierte Programmierung mit Objective-C.
Objective-C: Eine Obermenge von C
Objective-C ist eine strikte Obermenge von C -- jedes C-Programm kann ohne Änderung direkt durch einen Objective-C-Compiler kompiliert werden, und es ist völlig zulässig, C-Code in Objective-C zu verwenden. Objective-C wird als dünne Schicht über C beschrieben, da die ursprüngliche Absicht von Objective-C darin bestand, objektorientierte Funktionen zur C-Sprache hinzuzufügen.
Dateiendungen für Objective-C-Code
| Erweiterung | Inhaltstyp |
| .h | Header-Datei. Header-Dateien enthalten Deklarationen von Klassen, Typen, Funktionen und Konstanten. |
| .m | Quellcodedatei. Dies ist die typische Dateiendung für Quellcodedateien und kann sowohl Objective-C- als auch C-Code enthalten. |
| .mm | Quellcode-Datei. Eine Quellcode-Datei mit dieser Erweiterung kann zusätzlich zu Objective-C- und C-Code auch C++-Code enthalten. Verwenden Sie diese Erweiterung nur, wenn Sie wirklich C++-Klassen oder -Sprachmerkmale in Ihrem Objective-C-Code nutzen müssen. |
Wenn Sie Header-Dateien in Quellcode einbinden müssen, können Sie die standardmäßige #include-Compiler-Anweisung verwenden, aber Objective-C bietet einen besseren Weg. Die #import-Anweisung ist genau wie #include, außer dass sie sicherstellt, dass dieselbe Datei nur einmal eingebunden wird. Objective-C-Beispiele und -Dokumentation verwenden tendenziell #import, und Ihr Code sollte dies ebenso tun.
Syntax
Die objektorientierte Syntax von Objective-C stammt vom Smalltalk-Stil der Nachrichtenübermittlung. Alle andere nicht objektorientierte Syntax, einschließlich Variablentypen, Präprozessor, Ablaufsteuerung sowie Funktionsdeklaration und -aufruf, ist genau dieselbe wie in C. Einige gültige C-Syntax kann in Objective-C jedoch nicht dieselbe Bedeutung ausdrücken. Beispielsweise geben bestimmte boolesche Ausdrücke in C true zurück, aber wenn sie in Objective-C direkt mit YES verglichen werden, führt dies zu einem Fehler, da YES in Objective-C nur den Wert 1 hat.
Das erste Objective-C-Programm, basierend auf Xcode 4.3.1:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
NSLog(@"Hello World!");
}
return 0;
}
Nachrichtenübermittlung
Das bemerkenswerteste Merkmal von Objective-C ist das von Smalltalk geerbte Nachrichtenübermittlungsmodell, das sich deutlich vom heutigen Mainstream-Ansatz im C++-Stil unterscheidet. In Objective-C ist es treffender zu sagen, dass Objekte Nachrichten aneinander senden, statt Methoden aufeinander aufzurufen. Der Hauptunterschied zwischen den beiden Stilen liegt in der Aktion des Aufrufens von Methoden / Übermittelns von Nachrichten. In C++ ist die Beziehung zwischen Klassen und Methoden strikt und klar: Eine Methode muss zu einer Klasse gehören und wird zur Kompilierungszeit fest gebunden, daher ist es unmöglich, eine Methode aufzurufen, die in einer Klasse nicht existiert. In Objective-C ist die Beziehung zwischen Klassen und Nachrichten jedoch lockerer. Das Aufrufen einer Methode wird als Senden einer Nachricht an ein Objekt betrachtet, und alle Methoden werden als Antworten auf Nachrichten betrachtet. Die gesamte Nachrichtenverarbeitung wird erst zur Laufzeit dynamisch bestimmt, und es bleibt der Klasse überlassen, zu entscheiden, wie empfangene Nachrichten behandelt werden. Das heißt, eine Klasse ist nicht garantiert, auf jede empfangene Nachricht zu antworten. Wenn eine Klasse eine Nachricht empfängt, die sie nicht verarbeiten kann, löst das Programm nur eine Ausnahme aus; es kommt nicht zu einem Fehler oder Absturz.
In C++ lautet die Syntax zum Senden einer Nachricht an ein Objekt (oder zum Aufrufen einer Methode) wie folgt:
obj.method(argument);
In Objective-C wird sie wie folgt geschrieben:
[obj method: argument];
Diese beiden unterscheiden sich nicht nur syntaktisch, sondern auch im grundlegenden Verhalten.
Hier ist ein einfaches Beispiel einer car-Klasse, um das Nachrichtenübermittlungsmerkmal von Objective-C zu erklären:
[car fly];
Die typische C++-Interpretation ist "die fly-Methode der car-Klasse aufrufen". Wenn die car-Klasse keine fly-Methode definiert, wird die Kompilierung definitiv fehlschlagen. In Objective-C sollten wir es jedoch als "eine fly-Nachricht an das car-Objekt senden" interpretieren. fly ist die Nachricht und car ist der Empfänger der Nachricht. Nach dem Empfang der Nachricht entscheidet car, wie darauf reagiert wird. Wenn die car-Klasse eine fly-Methode definiert, wird der Code innerhalb der Methode ausgeführt; wenn die car-Klasse keine fly-Methode hat, wird das Programm trotzdem kompiliert, und zur Laufzeit wird eine Ausnahme ausgelöst.
Die beiden Stile haben jeweils ihre Vor- und Nachteile. C++ verlangt, dass alle Methoden entsprechende Aktionen haben, und die Bindung zur Kompilierungszeit macht Funktionsaufrufe sehr schnell. Der Nachteil ist, dass es nur eingeschränkte dynamische Bindungsfähigkeit über das Schlüsselwort virtual bieten kann. Objective-C ist von Natur aus zu Duck-Typing und dynamischer Bindung fähig, weil Nachrichten zur Laufzeit verarbeitet werden, sodass unbekannte Nachrichten an Objekte gesendet werden können. Es kann Nachrichten an eine gesamte Sammlung von Objekten senden, ohne den Typ jedes Objekts einzeln zu prüfen, und es verfügt außerdem über einen Nachrichtenweiterleitungsmechanismus. Darüber hinaus tut das Nullobjekt nil standardmäßig nichts, nachdem es eine Nachricht empfangen hat, sodass das Senden einer Nachricht an nil keinen Programmabsturz verursacht.
Zeichenketten
Als Obermenge von C unterstützt Objective-C die Zeichenkettenkonventionen der Sprache C. Das heißt, einzelne Zeichen werden in einfache Anführungszeichen gesetzt, und Zeichenketten werden in doppelte Anführungszeichen gesetzt. Der meiste Objective-C-Code verwendet jedoch im Allgemeinen keine Zeichenketten im C-Stil. Stattdessen übergeben die meisten Frameworks Zeichenketten als NSString-Objekte. Die Klasse NSString bietet einen Klassen-Wrapper für Zeichenketten, einschließlich aller Vorteile, die Sie erwarten würden, wie integrierte Speicherverwaltung zum Speichern von Zeichenketten beliebiger Länge, Unicode-Unterstützung, Formatierungswerkzeuge im printf-Stil und so weiter. Da diese Art von Zeichenkette so häufig verwendet wird, bietet Objective-C eine Merkhilfe, um bequem NSString-Objekte aus konstanten Werten zu erstellen. Um diese Merkhilfe zu verwenden, müssen Sie lediglich ein @-Symbol vor eine gewöhnliche Zeichenkette in doppelten Anführungszeichen setzen, wie im folgenden Beispiel gezeigt:
NSString* myString = @"My String\n"; NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"]; // 从一个C语言字符串创建Objective-C字符串 NSString* fromCString = [NSString stringWithCString:"A C string" encoding:NSASCIIStringEncoding];
Klasse
Wie bei allen anderen objektorientierten Sprachen sind Klassen in Objective-C die grundlegende Struktur zum Kapseln von Daten und der Verhaltensweisen, die auf diese Daten wirken. Ein Objekt ist eine Laufzeitinstanz einer Klasse; es enthält eine eigene Speicherkopie der von der Klasse deklarierten Instanzvariablen sowie Zeiger auf die Klassenmember. Die Objective-C-Klassenspezifikation besteht aus zwei Teilen: der Schnittstelle und der Implementierung. Der Schnittstellenteil enthält die Klassendeklaration, Definitionen von Instanzvariablen und Methoden, die zur Klasse gehören. Der Implementierungsteil enthält den tatsächlichen Code für die Klassenmethoden.
Das folgende Diagramm zeigt die Syntax für die Deklaration einer Klasse namens MyClass, die von der Basisklasse NSObject erbt. Eine Klassendeklaration beginnt immer mit der Compiler-Direktive @interface und endet mit der Compiler-Direktive @end. Nach dem Klassennamen (durch einen Doppelpunkt getrennt) steht der Name der Elternklasse. Die Instanzvariablen (oder Membervariablen) der Klasse werden in einem Codeblock deklariert, der von geschweiften Klammern umschlossen ist. Nach dem Instanzvariablenblock folgt eine Liste der von der Klasse deklarierten Methoden. Jede Instanzvariablen- und Methodendeklaration endet mit einem Semikolon.
Klassendefinitionsdateien folgen den Konventionen der Sprache C und haben die Endung .h; Implementierungsdateien verwenden die Endung .m.
Diagramm zur Klassendeklaration

Benutzeroberfläche
Der Schnittstellenteil definiert klar den Namen, die Datenmember und die Methoden der Klasse. Er beginnt mit dem Schlüsselwort @interface und endet mit @end.
@interface MyObject : NSObject {
int memberVar1; // 实体变量
id memberVar2;
}
+(return_type) class_method; // 类方法
-(return_type) instance_method1; // 实例方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end
Das +/-Zeichen vor einer Methode gibt den Funktionstyp an: Plus (+) steht für eine Klassenmethode, die ohne Instanz aufgerufen werden kann und einer statischen Memberfunktion in C++ ähnelt. Minus (-) steht für eine gewöhnliche Instanzmethode.
Hier ist ein Vergleich mit ähnlicher C++-Syntax, wie folgt:
class MyObject : public NSObject {
protected:
int memberVar1; // 实体变量
void * memberVar2;
public:
static return_type class_method(); // 類方法
return_type instance_method1(); // 实例方法
return_type instance_method2( int p1 );
return_type instance_method3( int p1, int p2 );
}
Wenn Objective-C eine neue Methode definiert, repräsentiert der Doppelpunkt (:) im Namen die Parameterübergabe, anders als in C, wo Klammern wie bei mathematischen Funktionen verwendet werden, um Parameter zu übergeben. Objective-C-Methoden erlauben, dass Parameter in den Namen eingestreut werden, statt alle am Ende des Methodennamens angehängt zu werden, was die Lesbarkeit des Programms verbessert. Nehmen wir die Methode zum Festlegen der RGB-Werte einer Farbe als Beispiel:
- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/ [myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/
Die Signatur dieser Methode lautet setColorToRed:Green:Blue:. Auf jeden Doppelpunkt folgt ein Parameter vom Typ float, der jeweils Rot, Grün und Blau repräsentiert.
Implementierung
Der Implementierungsblock enthält die Implementierungen öffentlicher Methoden sowie Definitionen privater Variablen und Methoden. Er beginnt mit dem Schlüsselwort @implementation und endet mit @end.
@implementation MyObject {
int memberVar3; //私有實體變數
}
+(return_type) class_method {
.... //method implementation
}
-(return_type) instance_method1 {
....
}
-(return_type) instance_method2: (int) p1 {
....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
....
}
@end
Erwähnenswert ist, dass nicht nur im Interface-Block Instanzvariablen definiert werden können, sondern auch der Implementierungsblock Instanzvariablen definieren kann. Der Unterschied zwischen beiden liegt in der Zugriffskontrolle. Instanzvariablen im Interface-Block sind standardmäßig protected, während im Implementierungsblock deklarierte Instanzvariablen standardmäßig private sind. Daher entspricht das Definieren privater Mitglieder im Implementierungsblock besser dem objektorientierten Kapselungsprinzip, denn auf diese Weise müssen die privaten Informationen der Klasse nicht in der öffentlichen Schnittstelle (der .h-Datei) offengelegt werden.
Objekte erstellen
In Objective-C erfordert das Erstellen eines Objekts zwei Nachrichten: alloc und init. alloc wird verwendet, um Speicher zuzuweisen, während init verwendet wird, um das Objekt zu initialisieren. Sowohl init als auch alloc sind in NSObject definierte Methoden. Erst nachdem das Elternobjekt diese beiden Nachrichten empfangen und korrekt beantwortet hat, ist das neue Objekt vollständig erstellt. Das folgende ist ein Beispiel:
MyObject * my = [[MyObject alloc] init];
In Objective-C 2.0 kann man, wenn das Erstellen eines Objekts keine Parameter erfordert, direkt new verwenden.
MyObject * my = [MyObject new];
Es ist lediglich eine syntaktische Kurzform; der Effekt ist genau derselbe.
Wenn Sie den Initialisierungsprozess anpassen möchten, können Sie die init-Methode überschreiben, um zusätzliche Arbeit hinzuzufügen. (Ihr Zweck ähnelt dem Konstruktor in C++.)
Methoden
Klassen in Objective-C können zwei Arten von Methoden deklarieren: Instanzmethoden und Klassenmethoden. Eine Instanzmethode ist eine Methode, die innerhalb des Gültigkeitsbereichs einer bestimmten Instanz der Klasse ausgeführt wird. Das heißt, bevor Sie eine Instanzmethode aufrufen, müssen Sie zunächst eine Instanz der Klasse erstellen. Eine Klassenmethode bedeutet im Vergleich dazu, dass Sie keine Instanz erstellen müssen.
Eine Methodendeklaration enthält den Methodentypbezeichner, den Rückgabetyp, ein oder mehrere Methodenselektor-Schlüsselwörter sowie Informationen zu Parametertyp und -name. Die folgende Abbildung zeigt die Deklaration der Instanzmethode insertObject:atIndex:. Die Deklaration beginnt mit einem Minuszeichen (-), das anzeigt, dass es sich um eine Instanzmethode handelt. Der tatsächliche Name der Methode (insertObject:atIndex:) ist die Verkettung aller Methodenselektor-Schlüsselwörter, einschließlich der Doppelpunkte. Der Doppelpunkt zeigt das Vorhandensein eines Parameters an. Wenn eine Methode keine Parameter hat, können Sie den Doppelpunkt nach dem ersten (und einzigen) Methodenselektor-Schlüsselwort weglassen. In diesem Beispiel hat die Methode zwei Parameter.
Syntax der Methodendeklaration

Wenn Sie eine Methode aufrufen möchten, senden Sie eine Nachricht an das entsprechende Objekt. Hierbei ist die Nachricht der Methodenselektor zusammen mit den an die Methode übergebenen Parameterinformationen. Alle an ein Objekt gesendeten Nachrichten werden dynamisch dispatcht, was polymorphes Verhalten in Objective-C-Klassen ermöglicht. Das heißt, wenn eine Unterklasse eine Methode mit demselben Selektor wie die Oberklasse definiert, empfängt die Unterklasse die Nachricht zuerst und kann die Nachricht dann optional an ihre Oberklasse weiterleiten (oder sich entscheiden, sie nicht weiterzuleiten).
Nachrichten werden in eckige Klammern ([ und ]) eingeschlossen. Innerhalb der Klammern steht das Objekt, das die Nachricht empfängt, auf der linken Seite und die Nachricht (einschließlich aller Parameter, die die Nachricht erfordert) auf der rechten Seite. Um beispielsweise die Nachricht insertObject:atIndex: an die Variable myArray zu senden, würden Sie die folgende Syntax verwenden:
[myArray insertObject:anObj atIndex:0];
Um zu vermeiden, zu viele lokale Variablen zum Speichern von Zwischenergebnissen deklarieren zu müssen, erlaubt Ihnen Objective-C die Verwendung verschachtelter Nachrichten. Der Rückgabewert jeder verschachtelten Nachricht kann als Argument oder Ziel für andere Nachrichten verwendet werden. Beispielsweise können Sie jede Variable im vorherigen Beispiel durch eine beliebige Nachricht ersetzen, die einen Wert dieses Typs liefert. Wenn Sie also ein anderes Objekt namens myAppObject mit Methoden haben, die auf das Array-Objekt zugreifen und ein Objekt in ein Array einfügen können, könnten Sie das vorherige Beispiel wie folgt umschreiben:
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
Obwohl die vorherigen Beispiele alle Nachrichten an eine Instanz einer Klasse senden, können Sie auch Nachrichten an die Klasse selbst senden. Wenn Sie eine Nachricht an eine Klasse senden, muss die von Ihnen angegebene Methode als Klassenmethode definiert sein, nicht als Instanzmethode. Sie können sich Klassenmethoden als einigermaßen ähnlich zu statischen Membern in C++-Klassen vorstellen (aber nicht genau gleich).
Typische Verwendungen von Klassenmethoden sind Fabrikmethoden zum Erstellen neuer Klasseninstanzen oder eine Möglichkeit, auf gemeinsam genutzte Informationen im Zusammenhang mit der Klasse zuzugreifen. Die Syntax zum Deklarieren von Klassenmethoden ist fast genau dieselbe wie für Instanzmethoden, mit nur einem kleinen Unterschied. Anders als Instanzmethoden, die ein Minuszeichen als Methodentypkennung verwenden, verwenden Klassenmethoden ein Pluszeichen (+).
Das folgende Beispiel zeigt, wie eine Klassenmethode als Fabrikmethode für eine Klasse dienen kann. Hier ist arrayWithCapacity eine Klassenmethode der Klasse NSMutableArray, die eine neue Instanz der Klasse zuweist und initialisiert und sie Ihnen dann zurückgibt.
NSMutableArray* myArray = nil; // nil 基本上等同于 NULL // 创建一个新的数组,并把它赋值给 myArray 变量 myArray = [NSMutableArray arrayWithCapacity:0];
Eigenschaften
Properties sind eine bequeme Möglichkeit, die Deklaration von Accessor-Methoden zu ersetzen. Properties erzeugen keine neue Instanzvariable in Ihrer Klassendeklaration. Sie sind lediglich eine Kurzschreibweise, um Methoden zu definieren, die auf vorhandene Instanzvariablen zugreifen. Eine Klasse, die Instanzvariablen offenlegt, kann die Property-Notation anstelle der Getter- und Setter-Syntax verwenden. Klassen können Properties auch verwenden, um "virtuelle" Instanzvariablen offenzulegen, die das Ergebnis einer dynamischen Berechnung eines Teils der Daten sind, anstatt tatsächlich in einer Instanzvariable gespeichert zu werden.
Tatsächlich kann man sagen, dass Properties Ihnen eine Menge redundanten Code ersparen. Da die meisten Accessor-Methoden auf ähnliche Weise implementiert werden, vermeiden Properties die Notwendigkeit, für jede von der Klasse offengelegte Instanzvariable unterschiedliche Getter und Setter bereitzustellen. Stattdessen geben Sie das gewünschte Verhalten mit einer Property-Deklaration an, und die tatsächlichen Getter- und Setter-Methoden werden zur Compilezeit basierend auf der Deklaration synthetisiert.
Property-Deklarationen sollten dort platziert werden, wo Methodendeklarationen in der Klassenschnittstelle stehen. Die grundlegende Definition verwendet die Compiler-Direktive @property, gefolgt von Typinformationen und dem Property-Namen. Sie können die Property auch mit benutzerdefinierten Optionen konfigurieren, die das Verhalten der Accessor-Methoden bestimmen. Das folgende Beispiel zeigt einige einfache Property-Deklarationen:
@interface Person : NSObject {
@public
NSString *name;
@private
int age;
}
@property(copy) NSString *name;
@property(readonly) int age;
-(id)initWithAge:(int)age;
@end
Die Accessor-Methoden der Property werden durch das Schlüsselwort @synthesize implementiert, das automatisch ein Paar Accessor-Methoden aus der Property-Deklaration generiert. Alternativ kann das Schlüsselwort @dynamic verwendet werden, um anzugeben, dass die Accessor-Methoden manuell vom Programmierer bereitgestellt werden.
@implementation Person
@synthesize name;
@dynamic age;
-(id)initWithAge:(int)initAge
{
age = initAge; // 注意:直接赋给成员变量,而非属性
return self;
}
-(int)age
{
return 29; // 注意:并非返回真正的年龄
}
@end
Auf Properties kann mit traditionellen Nachrichtenausdrücken, Punktausdrücken oder dem Methodenpaar "valueForKey:" / "setValue:forKey:" zugegriffen werden.
Person *aPerson = [[Person alloc] initWithAge: 53];
aPerson.name = @"Steve"; // 注意:点表达式,等于[aPerson setName: @"Steve"];
NSLog(@"Access by message (%@), dot notation(%@), property name(%@) and direct instance variable access (%@)",
[aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);
Um Punktausdrücke für den Zugriff auf die Properties einer Instanz zu verwenden, müssen Sie das Schlüsselwort "self" verwenden:
-(void) introduceMyselfWithProperties:(BOOL)useGetter
{
NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name)); // NOTE: getter vs. ivar access
}
Properties einer Klasse oder eines Protokolls können dynamisch gelesen werden.
int i;
int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount);
for ( i=0; i < propertyCount; i++ ) {
objc_property_t *thisProperty = propertyList + i;
const char* propertyName = property_getName(*thisProperty);
NSLog(@"Person has a property: '%s'", propertyName);
}
Schnelle Enumeration
Im Vergleich zur Verwendung von NSEnumerator-Objekten oder zum sequenziellen Aufzählen von Sammlungen bietet Objective-C 2.0 eine Syntax für schnelle Enumeration. In Objective-C 2.0 sind die folgenden Schleifen funktional äquivalent, weisen jedoch unterschiedliche Leistungsmerkmale auf.
// 使用NSEnumerator
NSEnumerator *enumerator = [thePeople objectEnumerator];
Person *p;
while ( (p = [enumerator nextObject]) != nil ) {
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用依次枚举
for ( int i = 0; i < [thePeople count]; i++ ) {
Person *p = [thePeople objectAtIndex:i];
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用快速枚举
for (Person *p in thePeople) {
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
Schnelle Enumeration kann effizienteren Code erzeugen als die Standard-Enumeration, da die von der Enumeration aufgerufenen Methoden durch Zeigerarithmetikoperationen ersetzt werden, die vom NSFastEnumeration-Protokoll bereitgestellt werden.
Protokolle
Ein Protokoll ist eine Liste von Methoden ohne Implementierungen. Jede Klasse kann ein Protokoll annehmen und genau diese Gruppe von Methoden implementieren.
Während der NeXT-Ära versuchte Objective-C einst, das Konzept der Mehrfachvererbung einzuführen, aber wegen des Aufkommens von Protokollen wurde es nicht umgesetzt.
Protokolle ähneln "Schnittstellen" in Java und C#. In Objective-C gibt es zwei Möglichkeiten, Protokolle zu definieren: "formale Protokolle", die vom Compiler garantiert werden, und "informelle Protokolle", die für bestimmte Zwecke eingeführt wurden.
Informelle ProtokolleEs ist eine Liste von Methoden, die optional implementiert werden können. Obwohl informelle Protokolle Protokolle genannt werden, sind sie tatsächlich eine Bezeichnung für eine nicht implementierte Kategorie, die an NSObject angehängt ist. Im Objective-C-Sprachmechanismus gibt es so etwas wie ein informelles Protokoll nicht. Nach OS X 10.6 verlieh die Einführung des @optional-Schlüsselworts formalen Protokollen dieselbe Fähigkeit, daher sind informelle Protokolle veraltet und werden nicht mehr verwendet.
Formale ProtokolleÄhnlich wie "Interfaces" in Java ist es eine Liste von Methoden. Jede Klasse kann deklarieren, dass sie ein bestimmtes Protokoll implementiert. Vor Objective-C 2.0 musste eine Klasse alle Methoden in dem Protokoll implementieren, dessen Konformität sie erklärt hatte; andernfalls meldete der Compiler einen Fehler, der angab, dass die Klasse nicht alle Methoden des Protokolls implementierte, dessen Konformität sie erklärt hatte. Objective-C 2.0 erlaubt es, bestimmte Methoden in einem Protokoll als optional zu markieren, sodass der Compiler die Implementierung dieser optionalen Methoden nicht erzwingt.
Protokolle werden in Cocoa häufig für Delegation und Ereignisauslösung verwendet. Beispielsweise enthält eine Textfeldklasse normalerweise ein Delegate-Objekt, das ein Protokoll implementieren kann, das eine Methode zur Implementierung der Autovervollständigung von Texteingaben enthalten kann. Wenn das Delegate-Objekt diese Methode implementiert, löst die Textfeldklasse zum geeigneten Zeitpunkt das Autovervollständigungsereignis aus und ruft diese Methode für die Autovervollständigungsfunktion auf.
Das Konzept der Protokolle in Objective-C ist nicht genau dasselbe wie das Konzept der Interfaces in Java. Das heißt, eine Klasse kann die in einem Protokoll enthaltenen Methoden implementieren, ohne zu deklarieren, dass sie zu diesem Protokoll konform ist, und dadurch faktisch dem Protokoll entspricht; dieser Unterschied ist für externen Code unsichtbar. Die Deklaration eines formalen Protokolls liefert keine Implementierungen; sie zeigt lediglich an, dass Klassen, die zu dem Protokoll konform sind, die Methoden des Protokolls implementiert haben, wodurch sichergestellt wird, dass die aufrufende Seite die Methoden sicher aufrufen kann.
Syntax
Ein Protokoll beginnt mit dem Schlüsselwort @protocol als Anfang des Blocks und endet mit @end, dazwischen steht die Methodenliste.
@protocol Locking - (void)lock; - (void)unlock; @end
Dies ist ein Beispiel für ein Protokoll. In der Multithread-Programmierung ist es oft notwendig sicherzustellen, dass eine gemeinsam genutzte Ressource jeweils nur von einem Thread verwendet werden kann, daher wird vor der Verwendung ein Lock an die Ressource angehängt. Das Obige ist ein Protokoll, das das Konzept eines "Locks" ausdrückt. Das Protokoll hat zwei Methoden, die Namen haben, aber noch nicht implementiert sind.
Im Folgenden deklariert SomeClass, dass es das Locking-Protokoll annimmt:
@interface SomeClass : SomeSuperClass <Locking> @end
Sobald SomeClass deklariert, dass es das Locking-Protokoll annimmt, ist SomeClass verpflichtet, die beiden Methoden im Locking-Protokoll zu implementieren.
@implementation SomeClass
- (void)lock {
// 實現lock方法...
}
- (void)unlock {
// 實現unlock方法...
}
@end
Da SomeClass bereits dem Locking-Protokoll entspricht, kann der Aufrufer sicher lock- oder unlock-Nachrichten an eine SomeClass-Instanzvariable senden, ohne befürchten zu müssen, dass sie nicht auf Nachrichten antworten kann.
Plug-ins sind ein weiteres Beispiel für die Verwendung abstrakter Definitionen, wodurch gewünschtes Verhalten definiert werden kann, ohne sich um die Implementierung des Plug-ins kümmern zu müssen.
Dynamische Typisierung
Ähnlich wie Smalltalk besitzt Objective-C dynamische Typisierung: Nachrichten können an jede Objektentität gesendet werden, unabhängig davon, ob die öffentliche Schnittstelle des Objekts eine entsprechende Methode enthält. Im Vergleich zu statisch typisierten Sprachen wie C++ unterbindet der Compiler Methodenaufrufe auf (void*)-Zeigern. In Objective-C kann man jedoch jede Nachricht an id senden (id ähnelt stark void*, ist aber strikt auf Objekte beschränkt); der Compiler gibt lediglich eine Warnung aus, dass "das Objekt möglicherweise nicht auf die Nachricht antwortet". Das Programm lässt sich weiterhin kompilieren, und was tatsächlich geschieht, hängt von der tatsächlichen Form des Objekts zur Laufzeit ab. Wenn das Objekt tatsächlich auf die Nachricht antworten kann, wird die entsprechende Methode trotzdem ausgeführt.
Nachdem ein Objekt eine Nachricht empfangen hat, gibt es drei mögliche Arten, sie zu behandeln. Erstens kann es auf die Nachricht antworten und die Methode ausführen. Wenn es nicht antworten kann, kann es die Nachricht an ein anderes Objekt weiterleiten. Wenn keines der oben Genannten erfolgt, muss es die Ausnahme behandeln, die wegen der Nichtantwort ausgelöst wird. Solange eine der drei Möglichkeiten umgesetzt wird, gilt die Nachricht als abgeschlossen und wird verworfen. Wenn eine Nachricht an "nil" (Null-Objektzeiger) gesendet wird, wird die Nachricht normalerweise ignoriert; je nach Compileroptionen kann sie eine Ausnahme auslösen.
Obwohl Objective-C dynamische Typisierungsfähigkeiten besitzt, kann auf Variablen weiterhin eine statische Typprüfung zur Kompilierungszeit angewendet werden. Die folgenden drei Deklarationen sind zur Laufzeit völlig äquivalent, aber jede liefert zunehmend explizitere Typinformationen. Die zusätzlichen Typinformationen ermöglichen es dem Compiler, Variablentypen zur Kompilierungszeit zu prüfen und bei nicht übereinstimmenden Variablen Warnungen auszugeben.
Die folgenden drei Methoden unterscheiden sich nur in der Form des Parameters:
- setMyValue:(id) foo;
Die id-Form gibt an, dass der Parameter "foo" eine Instanz einer beliebigen Klasse sein kann.
- setMyValue:(id <aProtocol>) foo;
id<aProtocol> gibt an, dass "foo" eine Instanz einer beliebigen Klasse sein kann, aber das Protokoll "aProtocol" annehmen muss.
- setMyValue:(NSNumber*) foo;
Diese Deklaration gibt an, dass "foo" eine Instanz von "NSNumber" sein muss.
Dynamische Typisierung ist eine leistungsstarke Funktion. Bei der Implementierung von Containerklassen in statisch typisierten Sprachen ohne Generics (wie Versionen vor Java 5) mussten Programmierer eine Containerklasse für Objekte eines generischen Typs schreiben und dann ständig zwischen generischem und tatsächlichem Typ umwandeln. Typumwandlung bricht jedoch die statische Typisierung; wenn man beispielsweise eine "Ganzzahl" schreibt und sie als "Zeichenkette" liest, verursacht das einen Laufzeitfehler. Solche Probleme werden durch Generics gelöst, aber Containerklassen erfordern konsistente Typen für ihre Inhaltssobjekte, während dynamisch typisierte Sprachen überhaupt kein solches Problem haben.
Weiterleitung
Objective-C ermöglicht das Senden einer Nachricht an ein Objekt, unabhängig davon, ob es darauf reagieren kann. Neben dem Beantworten oder Verwerfen von Nachrichten kann ein Objekt die Nachricht auch an ein Objekt weiterleiten, das darauf reagieren kann. Weiterleitung kann verwendet werden, um bestimmte Entwurfsmuster zu vereinfachen, wie das Beobachter-Muster oder das Proxy-Muster.
Die Objective-C-Laufzeit definiert ein Paar von Methoden in Object:
Weiterleitungsmethode:
- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC - (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems
Antwortmethode:
- (retval_t) performv:(SEL) sel :(arglist_t) args; // with GCC - (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems
Ein Objekt, das Weiterleitung implementieren möchte, muss lediglich die oben genannten Methoden durch neue Methoden überschreiben, um sein Weiterleitungsverhalten zu definieren. Es ist nicht nötig, die Antwortmethode performv:: neu zu schreiben, da diese Methode einfach eine Nachricht an das antwortende Objekt sendet und die Parameter übergibt. Unter ihnen ist der SEL-Typ der Typ von Nachrichten in Objective-C.
Der folgende Code demonstriert das grundlegende Konzept der Weiterleitung:
Code der Datei Forwarder.h:
#import <objc/Object.h>
@interface Forwarder : Object
{
id recipient; //该对象是我们希望转发到的对象。
}
@property (assign, nonatomic) id recipient;
@end
Code der Datei Forwarder.m:
#import "Forwarder.h"
@implementation Forwarder
@synthesize recipient;
- (retval_t) forward: (SEL) sel : (arglist_t) args
{
/*
*检查转发对象是否响应该消息。
*若转发对象不响应该消息,则不会转发,而产生一个错误。
*/
if([recipient respondsTo:sel])
return [recipient performv: sel : args];
else
return [self error:"Recipient does not respond"];
}
Code der Datei Recipient.h:
#import <objc/Object.h> // A simple Recipient object. @interface Recipient : Object - (id) hello; @end
Code der Datei Recipient.m:
#import "Recipient.h"
@implementation Recipient
- (id) hello
{
printf("Recipient says hello!\n");
return self;
}
@end
Code der Datei main.m:
#import "Forwarder.h"
#import "Recipient.h"
int main(void)
{
Forwarder *forwarder = [Forwarder new];
Recipient *recipient = [Recipient new];
forwarder.recipient = recipient; //Set the recipient.
/*
*转发者不响应hello消息!该消息将被转发到转发对象。
*(若转发对象响应该消息)
*/
[forwarder hello];
return 0;
}
Beim Kompilieren mit GCC meldet der Compiler:
$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc main.m: In function `main': main.m:12: warning: `Forwarder' does not respond to `hello' $
Wie bereits erwähnt, meldet der Compiler, dass die Klasse Forwarder nicht auf die hello-Nachricht reagiert. In diesem Fall kann diese Warnung ignoriert werden, da die Weiterleitung implementiert ist. Das Ausführen des Programms erzeugt die folgende Ausgabe:
$ ./a.out Recipient says hello!
Kategorien
Beim Entwurf von Objective-C ist ein Hauptaspekt die Wartung großer Code-Frameworks. Erfahrungen mit strukturierter Programmierung zeigen, dass ein wichtiger Weg zur Verbesserung von Code darin besteht, ihn in kleinere Teile zu zerlegen. Objective-C übernimmt und erweitert das Konzept der „Kategorie“ aus der Smalltalk-Implementierung, um das Ziel der Zerlegung von Code zu erreichen.
Eine Kategorie kann Methodenimplementierungen in eine Reihe separater Dateien zerlegen. Programmierer können eine Gruppe verwandter Methoden in einer Kategorie zusammenfassen, um das Programm lesbarer zu machen. Beispielsweise kann der Zeichenkettenklasse eine Kategorie namens „Rechtschreibprüfung“ hinzugefügt werden, und der zugehörige Rechtschreibprüfungscode kann in dieser Kategorie abgelegt werden.
Darüber hinaus werden Methoden in einer Kategorie zur Laufzeit zur Klasse hinzugefügt. Diese Funktion ermöglicht es Programmierern, vorhandenen Klassen Methoden hinzuzufügen, ohne den ursprünglichen Code zu besitzen oder die ursprüngliche Klasse neu zu kompilieren. Wenn beispielsweise die vom System bereitgestellte Implementierung der Zeichenkettenklasse keine Rechtschreibprüfungsfunktionalität enthält, kann eine solche Funktionalität hinzugefügt werden, ohne den ursprünglichen Code der Zeichenkettenklasse zu ändern.
Zur Laufzeit unterscheiden sich Methoden in einer Kategorie nicht von den ursprünglichen Methoden der Klasse, und ihr Code kann auf alle Instanzvariablen zugreifen, einschließlich privater Instanzvariablen der Klasse.
Wenn eine Kategorie eine Methode mit demselben Namen wie eine vorhandene Methode in der Klasse deklariert, wird die Methode in der Kategorie aufgerufen. Daher können Kategorien nicht nur Methoden zu einer Klasse hinzufügen, sondern auch ursprüngliche Methoden ersetzen. Diese Funktion kann verwendet werden, um Fehler im ursprünglichen Code zu beheben, und kann sogar das Verhalten der ursprünglichen Klassen in einem Programm grundlegend ändern. Wenn Methoden in zwei Kategorien denselben Namen haben, ist unvorhersehbar, welche Methode aufgerufen wird.
Andere Sprachen haben ebenfalls versucht, diese Sprachfunktion durch verschiedene Methoden hinzuzufügen. TOM geht in dieser Hinsicht weiter und ermöglicht nicht nur das Hinzufügen von Methoden, sondern auch von Mitgliedsvariablen. Andere Sprachen verwenden deklarationsorientierte Lösungen, die bemerkenswerteste ist die Sprache Self.
C# und Visual Basic.NET implementieren ähnliche Funktionalität durch Erweiterungsmethoden und partielle Klassen. Ruby und einige dynamische Sprachen nennen diese Technik "monkey patch".
Beispiel für die Verwendung von Kategorien
Dieses Beispiel erstellt eine Integer-Klasse, die selbst nur die integer-Eigenschaft definiert, und fügt dann zwei Kategorien hinzu, Arithmetic und Display, um die Funktionalität der Klasse zu erweitern. Obwohl Kategorien auf die privaten Mitglieder der Klasse zugreifen können, ist es normalerweise eine bessere Praxis, über Property-Accessor-Methoden auf sie zuzugreifen, wodurch die Kategorien unabhängiger von der ursprünglichen Klasse werden. Dies ist eine typische Verwendung von Kategorien – eine andere Verwendung besteht darin, Methoden in der ursprünglichen Klasse durch Kategorien zu ersetzen, obwohl die Verwendung von Kategorien anstelle von Vererbung zum Ersetzen von Methoden nicht als gute Praxis angesehen wird.
Code der Datei Integer.h:
#import <objc/Object.h>
@interface Integer : Object
{
@private
int integer;
}
@property (assign, nonatomic) integer;
@end
Code der Datei Integer.m:
#import "Integer.h" @implementation Integer @synthesize integer; @end
Code der Datei Arithmetic.h:
#import "Integer.h" @interface Integer(Arithmetic) - (id) add: (Integer *) addend; - (id) sub: (Integer *) subtrahend; @end
Code der Datei Arithmetic.m:
#import "Arithmetic.h"
@implementation Integer(Arithmetic)
- (id) add: (Integer *) addend
{
self.integer = self.integer + addend.integer;
return self;
}
- (id) sub: (Integer *) subtrahend
{
self.integer = self.integer - subtrahend.integer;
return self;
}
@end
Code der Datei Display.h:
#import "Integer.h" @interface Integer(Display) - (id) showstars; - (id) showint; @end
Code der Datei Display.m:
#import "Display.h"
@implementation Integer(Display)
- (id) showstars
{
int i, x = self.integer;
for(i=0; i < x; i++)
printf("*");
printf("\n");
return self;
}
- (id) showint
{
printf("%d\n", self.integer);
return self;
}
@end
Code der Datei main.m:
#import "Integer.h"
#import "Arithmetic.h"
#import "Display.h"
int
main(void)
{
Integer *num1 = [Integer new], *num2 = [Integer new];
int x;
printf("Enter an integer: ");
scanf("%d", &x);
num1.integer = x;
[num1 showstars];
printf("Enter an integer: ");
scanf("%d", &x);
num2.integer = x;
[num2 showstars];
[num1 add:num2];
[num1 showint];
return 0;
}
Kompilieren Sie mit dem folgenden Befehl:
gcc -x objective-c main.m Integer.m Arithmetic.m Display.m -lobjc
Zur Kompilierungszeit können Sie experimentieren, indem Sie #import "Arithmetic.h", den Befehl [num1 add:num2] und die Datei Arithmetic.m weglassen. Das Programm läuft trotzdem, was zeigt, dass Kategorien dynamisch und bei Bedarf geladen werden können; wenn die Funktionalität einer Kategorie nicht benötigt wird, können Sie sie einfach nicht kompilieren.
Garbage Collection
Objective-C 2.0 bietet einen optionalen Garbage Collector. Im rückwärtskompatiblen Modus wandelt die Objective-C-Laufzeitumgebung Referenzzählungsoperationen wie "retain" und "release" in No-Ops um. Wenn die Garbage Collection aktiviert ist, unterliegen alle Objekte der Verwaltung des Collectors. Gewöhnliche C-Zeiger können mit "__strong" versehen werden, was anzeigt, dass das Objekt, auf das der Zeiger zeigt, noch verwendet wird. Mit "__weak" gekennzeichnete Zeiger werden vom Collector nicht gezählt und werden in "nil" umgeschrieben, wenn das Objekt eingesammelt wird. Die Objective-C-2.0-Implementierung unter iOS enthält keinen Garbage Collector. Der Garbage Collector läuft in einem Hintergrundthread mit niedriger Priorität und kann pausieren, wenn der Benutzer agiert, wodurch eine gute Benutzererfahrung erhalten bleibt.
Referenzlink: https://zh.wikipedia.org/wiki/Objective-C