Kategorien sind eine Funktion der Objective-C-Sprache, mit der Sie einer vorhandenen Klasse neue Methoden hinzufügen können, ähnlich wie C # -Erweiterungen. Verwechseln Sie die C # -Erweiterungen jedoch nicht mit den Objective-C-Erweiterungen. Die Erweiterungen von Objective-C sind ein Sonderfall von Kategorien, mit denen Sie Methoden definieren können, die in der deklariert werden müssen Hauptimplementierungsblock.
Dies sind leistungsstarke Funktionen, die viele Einsatzmöglichkeiten bieten. Erstens ermöglichen Kategorien, die Schnittstelle und Implementierung einer Klasse in mehrere Dateien aufzuteilen, was die dringend benötigte Modularität für größere Projekte bietet. Zweitens können Sie mithilfe von Kategorien Fehler in einer vorhandenen Klasse beheben (z., NSString
) ohne die Notwendigkeit einer Unterklasse. Drittens stellen sie eine effektive Alternative zu den geschützten und privaten Methoden dar, die in C # und anderen Simula-ähnlichen Sprachen zu finden sind.
EIN Kategorie ist eine Gruppe verwandter Methoden für eine Klasse. Alle in einer Kategorie definierten Methoden sind über die Klasse verfügbar, als wären sie in der Hauptschnittstellendatei definiert. Nehmen Sie als Beispiel das Person
Klasse, mit der wir in diesem Buch gearbeitet haben. Wenn dies ein großes Projekt wäre, Person
Möglicherweise gibt es Dutzende von Methoden, die vom grundlegenden Verhalten über die Interaktion mit anderen Personen bis zur Identitätsprüfung reichen. Die API erfordert möglicherweise, dass alle diese Methoden für eine einzige Klasse verfügbar sind. Entwickler können jedoch die Verwaltung wesentlich vereinfachen, wenn jede Gruppe in einer separaten Datei gespeichert ist. Darüber hinaus ist es bei Kategorien nicht erforderlich, die gesamte Klasse jedes Mal neu zu kompilieren, wenn Sie eine einzelne Methode ändern. Dies kann bei sehr großen Projekten Zeit sparen.
Lassen Sie uns einen Blick darauf werfen, wie Kategorien dazu verwendet werden können. Wir beginnen mit einer normalen Klassenschnittstelle und einer entsprechenden Implementierung:
// Person.h @interface Person: NSObject @interface Person: NSObject @property (schreibgeschützt) NSMutableArray * friends; @ property (copy) NSString * name; - (nichtig) sagen hallo; - (nichtig) sagen, auf Wiedersehen; @ende // Person.m #import "Person.h" @implementation Person @synthesize name = _name; @ synthetisieren Freunde = _friends; - (id) init self = [super init]; if (self) _friends = [[NSMutableArray-Zuordnung] init]; return self; - (void) sayHello NSLog (@ "Hallo, sagt% @.", _name); - (void) sayGoodbye NSLog (@ "Auf Wiedersehen sagt% @.", _name); @Ende
Nichts Neues hier - nur ein Person
Klasse mit zwei Eigenschaften (die Freunde
Eigenschaft wird von unserer Kategorie verwendet) und zwei Methoden. Als Nächstes verwenden wir eine Kategorie, um einige Methoden für die Interaktion mit anderen zu speichern Person
Instanzen. Erstellen Sie eine neue Datei, verwenden Sie statt einer Klasse jedoch die Ziel-C-Kategorie Vorlage. Benutzen Beziehungen für den Kategorienamen und Person für die Kategorie auf Feld:
Wie erwartet werden zwei Dateien erstellt: ein Header für die Schnittstelle und eine Implementierung. Beide werden jedoch etwas anders aussehen als das, mit dem wir gearbeitet haben. Schauen wir uns zunächst die Schnittstelle an:
// Person + Relations.h #import#import "Person.h" @interface Person (Relations) - (void) addFriend: (Person *) aFriend; - (nichtig) removeFriend: (Person *) aFriend; - (void) sayHelloToFriends; @Ende
Anstelle des Normalen @Schnittstelle
Deklaration, fügen wir den Kategorienamen in Klammern nach dem Klassennamen ein, den wir erweitern. Ein Kategoriename kann beliebig sein, solange er nicht mit anderen Kategorien für dieselbe Klasse in Konflikt steht. Eine Kategorie Datei name sollte der Klassenname sein, gefolgt von einem Pluszeichen, gefolgt vom Namen der Kategorie (z., Person + Beziehung.h
).
Dies definiert also das Interface unserer Kategorie. Alle Methoden, die wir hier hinzufügen, werden dem Original hinzugefügt Person
Klasse zur Laufzeit. Es erscheint als ob das Freund hinzufügen:
, Freund entfernen:
, und sayHelloToFriends
Methoden sind alle in definiert Person.h
, Wir können jedoch unsere Funktionalität gekapselt und wartbar halten. Beachten Sie auch, dass Sie die Kopfzeile für die ursprüngliche Klasse importieren müssen, Person.h
. Die Kategorieimplementierung folgt einem ähnlichen Muster:
// Person + Relations.m #import "Person + Relations.h" @implementation Person (Relations) - (void) addFriend: (Person *) aFriend [[Freunde] addObject: aFriend]; - (void) removeFriend: (Person *) aFriend [[selbstfreunde] removeObject: aFriend]; - (void) sayHelloToFriends für (Person * Freund in [Selbstfreunden]) NSLog (@ "Hallo,% @!", [Name des Freundes]); @Ende
Dies implementiert alle Methoden in Person + Beziehung.h
. Genau wie die Benutzeroberfläche der Kategorie wird der Kategoriename in Klammern nach dem Klassennamen angezeigt. Der Kategoriename in der Implementierung sollte mit dem in der Schnittstelle übereinstimmen.
Beachten Sie auch, dass Sie keine zusätzlichen Eigenschaften oder Instanzvariablen in einer Kategorie definieren können. Kategorien müssen sich auf Daten beziehen, die in der Hauptklasse gespeichert sind (Freunde
in diesem Fall).
Es ist auch möglich, die in enthaltene Implementierung zu überschreiben Person.m
indem Sie einfach die Methode in neu definieren Person + Beziehung.m
. Dies kann verwendet werden, um eine vorhandene Klasse mit einem Affen zu patchen. Es wird jedoch nicht empfohlen, wenn Sie eine alternative Lösung für das Problem haben, da die durch die Kategorie definierte Implementierung nicht überschrieben werden kann. Das heißt, Kategorien sind im Gegensatz zur Klassenhierarchie eine flache Organisationsstruktur. Wenn Sie dieselbe Methode in zwei separaten Kategorien implementieren, kann die Laufzeit nicht herausfinden, welche Methode sie verwenden soll.
Die einzige Änderung, die Sie vornehmen müssen, um eine Kategorie zu verwenden, ist das Importieren der Header-Datei der Kategorie. Wie Sie im folgenden Beispiel sehen können, ist die Person
Klasse hat Zugriff auf die in Person.h
zusammen mit denen, die in der Kategorie definiert sind Person + Beziehung.h
:
// main.m #import#import "Person.h" #import "Person + Relations.h" int main (int argc, const char * argv []) @autoreleasepool Person * joe = [[Personallokation] init]; joe.name = @ "Joe"; Person * Rechnung = [[Personenzuordnung] init]; bill.name = @ "bill"; Person * mary = [[Personenzuweisung] init]; mary.name = @ "Mary"; [Joe sayHello]; [Joe AddFriend: Rechnung]; [Joe AddFriend: Mary]; [joe sayHelloToFriends]; return 0;
Und das ist alles, was Sie zum Erstellen von Kategorien in Objective-C benötigen.
Wiederholen, alles Objective-C-Methoden sind öffentlich - es gibt kein Sprachkonstrukt, um sie als privat oder geschützt zu kennzeichnen. Anstatt "echte" geschützte Methoden zu verwenden, können Objective-C-Programme Kategorien mit dem Schnittstellen- / Implementierungsparadigma kombinieren, um dasselbe Ergebnis zu erzielen.
Die Idee ist einfach: Deklarieren Sie "protected" Methoden als Kategorie in einer separaten Header-Datei. Dies gibt Unterklassen die Möglichkeit, die geschützten Methoden "zuzulassen", während nicht verknüpfte Klassen wie üblich die "öffentliche" Header-Datei verwenden. Nehmen Sie zum Beispiel einen Standard Schiff
Schnittstelle:
// Ship.h #import@Interface Ship: NSObject - (void) schießen; @Ende
Wie wir schon oft gesehen haben, definiert dies eine öffentliche Methode namens schießen
. A zu erklären geschützt Methode, müssen wir eine erstellen Schiff
Kategorie in einer dedizierten Header-Datei:
// Ship_Protected.h #import@Interface Ship (Protected) - (void) preparToShoot; @Ende
Alle Klassen, die Zugriff auf die geschützten Methoden benötigen (die Superklasse und alle Unterklassen), können einfach importiert werden Ship_Protected.h
. Zum Beispiel die Schiff
Die Implementierung sollte ein Standardverhalten für die geschützte Methode definieren:
// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementation Ship BOOL _gunIsReady; - (void) schießen if (! _gunIsReady) [self preparToShoot]; _gunIsReady = YES; NSLog (@ "Abfeuern!"); - (void) preparToShoot // Einige private Funktionen ausführen. NSLog (@ "Hauptwaffe vorbereiten ..."); @Ende
Beachten Sie, wenn wir nicht importiert hätten Ship_Protected.h
, diese prepareToShoot
Die Implementierung wäre eine private Methode, wie in der Kapitel Methoden. Ohne eine geschützte Kategorie können Unterklassen nicht auf diese Methode zugreifen. Lassen Sie uns die Schiff
um zu sehen, wie das funktioniert. Wir werden es nennen ResearchShip
:
// ResearchShip.h #import "Ship.h" @interface ResearchShip: Ship - (void) extendTelescope; @Ende
Dies ist eine normale Unterklassenschnittstelle, die sollte nicht Importieren Sie den geschützten Header, da dadurch die geschützten Methoden für jeden Importeur verfügbar sind ResearchShip.h
, Genau das versuchen wir zu vermeiden. Schließlich importiert die Implementierung für die Unterklasse die geschützten Methoden und überschreibt sie (optional):
// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation ResearchShip - (void) extendTelescope NSLog (@ "Das Teleskop erweitern"); // Geschützte Methode überschreiben - (void) prepareToShoot NSLog (@ "Oh schießen! Wir müssen Waffen finden!"); @Ende
Um den geschützten Status der Methoden in zu erzwingen Ship_Protected.h
, andere Klassen dürfen ihn nicht importieren. Sie importieren nur die normalen "öffentlichen" Schnittstellen der Superklasse und Unterklasse:
// main.m #import#import "Ship.h" #import "ResearchShip.h" int main (int argc, const char * argv []) @autoreleasepool Ship * genericShip = [[Schiffszuteilung] init]; [genericShip shoot]; Ship * discoveryOne = [[ResearchShip-Zuordnung] init]; [discoveryOne shoot]; return 0;
Da keiner main.m
, Ship.h
, Noch ResearchShip.h
Importieren Sie die geschützten Methoden, auf diesen Code kann nicht zugegriffen werden. Versuchen Sie, eine [discoveryOne preparToShoot]
Methode-es wird einen Compiler-Fehler, da die prepareToShoot
Deklaration ist nirgends zu finden.
Zusammenfassend können geschützte Methoden emuliert werden, indem sie in eine dedizierte Header-Datei eingefügt und diese Header-Datei in die Implementierungsdateien importiert wird, die Zugriff auf die geschützten Methoden erfordern. Keine anderen Dateien sollten den geschützten Header importieren.
Der hier vorgestellte Workflow ist zwar ein vollständig gültiges Organisationswerkzeug, bedenken Sie jedoch, dass Objective-C niemals geschützte Methoden unterstützen sollte. Stellen Sie sich dies als eine alternative Methode zum Strukturieren einer Objective-C-Methode vor und nicht als direkten Ersatz für geschützte Methoden im C # / Simula-Stil. Es ist oft besser, nach einer anderen Möglichkeit zu suchen, um Ihre Klassen zu strukturieren, als den Objective-C-Code zu zwingen, sich wie ein C # -Programm zu verhalten.
Eines der größten Probleme bei Kategorien ist, dass Sie in Kategorien definierte Methoden für dieselbe Klasse nicht zuverlässig überschreiben können. Zum Beispiel, wenn Sie eine definiert haben Freund hinzufügen:
Klasse in Person (Beziehung)
und entschied sich später, das zu ändern Freund hinzufügen:
Umsetzung über a Person (Sicherheit)
Kategorie gibt es für die Laufzeitumgebung keine Möglichkeit zu wissen, welche Methode sie verwenden sollte, da Kategorien definitionsgemäß eine flache Organisationsstruktur sind. In solchen Fällen müssen Sie zum traditionellen Unterklassen-Paradigma zurückkehren.
Beachten Sie außerdem, dass eine Kategorie keine Instanzvariablen hinzufügen kann. Das bedeutet, dass Sie keine neuen Eigenschaften in einer Kategorie deklarieren können, da sie nur in der Hauptimplementierung synthetisiert werden könnten. Während eine Kategorie technisch auf die Instanzvariablen ihrer Klassen zugreifen kann, ist es besser, über ihre öffentliche Schnittstelle auf sie zuzugreifen, um die Kategorie vor potenziellen Änderungen in der Hauptimplementierungsdatei zu schützen.
Erweiterungen (auch genannt Klassenerweiterungen) sind eine spezielle Art von Kategorie, für die ihre Methoden in der definiert werden müssen Main Implementierungsblock für die zugeordnete Klasse im Gegensatz zu einer in einer Kategorie definierten Implementierung. Dies kann verwendet werden, um öffentlich deklarierte Eigenschaftsattribute zu überschreiben. Beispielsweise ist es manchmal praktisch, eine schreibgeschützte Eigenschaft in eine schreibgeschützte Eigenschaft in einer Klassenimplementierung zu ändern. Betrachten Sie die normale Schnittstelle für a Schiff
Klasse:
Enthaltenes Codebeispiel: Erweiterungen
// Ship.h #import#import "Person.h" @interface Schiff: NSObject @property (stark, schreibgeschützt) Person * Kapitän; - (id) initWithCaptain: (Person *) Kapitän; @Ende
Es ist möglich, das zu überschreiben @Eigentum
Definition innerhalb einer Klassenerweiterung. Dies gibt Ihnen die Möglichkeit, die Eigenschaft erneut als zu deklarieren lesen Schreiben
in der Implementierungsdatei. Syntaktisch sieht eine Erweiterung wie eine leere Kategoriedeklaration aus:
// Ship.m #import "Ship.h" // Die Klassenerweiterung. @Interface Ship () @Property (Strong, Readwrite) Person * Kapitän; @end // Die Standardimplementierung. @implementation Ship @synthesize captain = _captain; - (id) initWithCaptain: (Person *) Kapitän self = [super init]; if (self) // Das wird wegen der Erweiterung funktionieren. [self setCaptain: captain]; return self; @Ende
Beachten Sie das ()
an den Klassennamen nach dem angehängt @Schnittstelle
Richtlinie. Dies ist es, was es als Erweiterung und nicht als normale Schnittstelle oder Kategorie bezeichnet. Alle Eigenschaften oder Methoden, die in der Erweiterung angezeigt werden Muss im Hauptimplementierungsblock für die Klasse deklariert werden. In diesem Fall fügen wir keine neuen Felder hinzu. Wir überschreiben ein vorhandenes. Aber im Gegensatz zu Kategorien, Erweiterungen können Fügen Sie einer Klasse zusätzliche Instanzvariablen hinzu. Deshalb können wir Eigenschaften in einer Klassenerweiterung deklarieren, jedoch nicht in einer Kategorie.
Weil wir das wieder deklariert haben Kapitän
Eigenschaft mit einem lesen Schreiben
Attribut, das initWithCaptain:
Methode kann das verwenden setCaptain:
Accessor auf sich selbst. Wenn Sie die Erweiterung löschen, kehrt die Eigenschaft zum schreibgeschützten Status zurück, und der Compiler beschwert sich. Kunden mit der Schiff
Klasse soll die Implementierungsdatei nicht importieren, also die Kapitän
Die Eigenschaft bleibt schreibgeschützt.
#einführen#import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * heywood = [[Personallokation] init]; heywood.name = @ "Heywood"; Schiff * discoveryOne = [[Schiffszuteilung] initWithCaptain: heywood]; NSLog (@ "% @", [discoveryOne captain] .name); Person * dave = [[Personenzuweisung] init]; dave.name = @ "Dave"; // Das funktioniert NICHT, da die Eigenschaft noch schreibgeschützt ist. [discoveryOne setCaptain: dave]; return 0;
Ein weiterer häufiger Anwendungsfall für Erweiterungen ist die Deklaration privater Methoden. Im vorigen Kapitel haben wir gesehen, wie private Methoden deklariert werden können, indem sie einfach an einer beliebigen Stelle in der Implementierungsdatei hinzugefügt werden. Vor Xcode 4.3 war dies jedoch nicht der Fall. Die kanonische Methode zum Erstellen einer privaten Methode bestand darin, sie mithilfe einer Klassenerweiterung weiterzuleiten. Schauen wir uns das an, indem wir die Schiff
Header aus dem vorherigen Beispiel:
// Ship.h #import@Interface Ship: NSObject - (void) schießen; @Ende
Als nächstes werden wir das Beispiel, das wir verwendet haben, als wir private Methoden in der Kapitel Methoden. Anstatt einfach das Private hinzuzufügen prepareToShoot
Methode zur Implementierung, müssen wir sie in einer Klassenerweiterung weiterleiten.
// Ship.m #import "Ship.h" // Die Klassenerweiterung. @Interface Ship () - (void) preparToShoot; @end // Der Rest der Implementierung. @implementation Ship BOOL _gunIsReady; - (void) schießen if (! _gunIsReady) [self preparToShoot]; _gunIsReady = YES; NSLog (@ "Abfeuern!"); - (void) preparToShoot // Einige private Funktionen ausführen. NSLog (@ "Hauptwaffe vorbereiten ..."); @Ende
Der Compiler sorgt dafür, dass die Erweiterungsmethoden im Hauptimplementierungsblock implementiert werden, weshalb er als Vorwärtsdeklaration fungiert. Da die Erweiterung jedoch in der Implementierungsdatei gekapselt ist, sollten andere Objekte davon nie erfahren, sodass private Methoden auf andere Weise nachgeahmt werden können. Während neuere Compiler diese Probleme ersparen, ist es immer noch wichtig zu verstehen, wie Klassenerweiterungen funktionieren, da bis vor kurzem private Methoden in Objective-C-Programmen üblich waren.
In diesem Kapitel wurden zwei der einzigartigeren Konzepte der Programmiersprache Objective-C behandelt: Kategorien und Erweiterungen. Kategorien sind eine Möglichkeit, die API vorhandener Klassen zu erweitern, und Erweiterungen können hinzugefügt werden erforderlich Methoden an die API außerhalb der Hauptschnittstellendatei. Beide waren ursprünglich so konzipiert, dass sie die Verwaltung großer Codebasis erleichtern.
Das nächste Kapitel setzt unsere Reise durch die Organisationsstrukturen von Objective-C fort. Wir werden lernen, wie man ein Protokoll definiert, eine Schnittstelle, die von einer Vielzahl von Klassen implementiert werden kann.
Diese Lektion stellt ein Kapitel von Objective-C Succinctly dar, ein kostenloses eBook des Teams von Syncfusion.