Der beste Weg, um private Methoden für eine Klasse in Objective-C zu definieren

355

Ich habe gerade angefangen, Objective-C zu programmieren, und mit einem Hintergrund in Java frage ich mich, wie Leute, die Objective-C-Programme schreiben, mit privaten Methoden umgehen.

Ich verstehe, dass es verschiedene Konventionen und Gewohnheiten geben kann, und denke über diese Frage als Aggregator der besten Techniken nach, die Menschen verwenden, um mit privaten Methoden in Objective-C umzugehen.

Bitte geben Sie bei der Veröffentlichung ein Argument für Ihren Ansatz an. Warum ist es gut? Welche Nachteile hat es (die Sie kennen) und wie gehen Sie damit um?


Was meine bisherigen Erkenntnisse betrifft.

Es ist möglich, in der Datei MyClass.m definierte Kategorien [z. B. MyClass (Private)] zu verwenden, um private Methoden zu gruppieren.

Dieser Ansatz hat zwei Probleme:

  1. Xcode (und Compiler?) Überprüft nicht, ob Sie alle Methoden in der privaten Kategorie im entsprechenden @ Implementierungsblock definieren
  2. Sie müssen @interface, das Ihre private Kategorie deklariert, am Anfang der Datei MyClass.m einfügen, andernfalls beschwert sich Xcode mit einer Nachricht wie "self antwortet möglicherweise nicht auf die Nachricht" privateFoo ".

Die erste Ausgabe kann mit einer leeren Kategorie [z. B. MyClass ()] umgangen werden.
Der zweite stört mich sehr. Ich würde gerne sehen, dass private Methoden am Ende der Datei implementiert (und definiert) werden. Ich weiß nicht, ob das möglich ist.

Yurii Soldak
quelle
1
Leute könnten diese Frage interessant finden: stackoverflow.com/questions/2158660/…
bbum
4
Warum nicht einfach die Deklaration der privaten Methode weglassen ?
ma11hew28

Antworten:

436

Wie andere bereits gesagt haben, gibt es in Objective-C keine private Methode. Doch in Objective-C beginnen 2.0 (was bedeutet , Mac OS X Leopard, iPhone OS 2.0 und höher) Sie können eine Kategorie mit einem leeren Namen (dh erstellen @interface MyClass ()) genannt Klassenerweiterung . Das Besondere an einer Klassenerweiterung ist, dass die Methodenimplementierungen @implementation MyClassmit den öffentlichen Methoden identisch sein müssen . Also strukturiere ich meine Klassen so:

In der .h-Datei:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

Und in der .m-Datei:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

Ich denke, der größte Vorteil dieses Ansatzes besteht darin, dass Sie Ihre Methodenimplementierungen nach Funktionalität gruppieren können, nicht nach der (manchmal willkürlichen) öffentlichen / privaten Unterscheidung.

Alex
quelle
8
und es wird eine "MYClass reagiert möglicherweise nicht auf '-myPrivateMethod-" generiert, keine Ausnahme / ein Fehler.
Özgür
2
Dies wird tatsächlich im Apple Boilerplate-Code angezeigt. ++
Chris Trahey
75
Mit dem LLVM 4-Compiler und höher müssen Sie dies nicht einmal tun. Sie können sie einfach in Ihrer Implementierung definieren, ohne sie in eine Klassenerweiterung einfügen zu müssen.
Abizern
1
Wenn Sie die von @Comptrol erwähnten Warnungen erhalten, liegt dies daran, dass Sie eine Methode unter und nicht über einer anderen Methode definiert haben, die sie aufruft (siehe Andys Antwort) - und diese Warnungen auf eigene Gefahr ignorieren. Ich habe diesen Fehler gemacht und der Compiler hat sich gut durchgeschlagen, bis ich einen Aufruf wie diesen verschachtelt habe: if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...Dann kam fWidthCombined immer als 0 durch.
Wienke
6
@Wienke Es ist nicht mehr nötig, sich um die Bestellung zu kümmern. Neuere Versionen von LLVM finden die Methode auch dann, wenn sie dort angezeigt wird, wo sie aufgerufen wird.
Steve Waddicor
52

Es gibt in Objective-C keine "private Methode", wenn die Laufzeit herausfinden kann, welche Implementierung sie verwenden soll. Das heißt aber nicht, dass es keine Methoden gibt, die nicht Teil der dokumentierten Schnittstelle sind. Für diese Methoden denke ich, dass eine Kategorie in Ordnung ist. Anstatt das @interfacewie in Punkt 2 oben in die .m-Datei einzufügen, würde ich es in eine eigene .h-Datei einfügen. Eine Konvention folge ich (und habe an anderer Stelle gesehen, ich glaube , es ist ein Apple - Konvention als Xcode jetzt automatische Unterstützung für sie gibt) ist eine solche Datei nach seiner Klasse und Kategorie mit einem + zu nennen , sie zu trennen, so @interface GLObject (PrivateMethods)finden Sie in GLObject+PrivateMethods.h. Der Grund für die Bereitstellung der Header-Datei ist, dass Sie sie in Ihre Unit-Test-Klassen importieren können :-).

Übrigens, was das Implementieren / Definieren von Methoden am Ende der .m-Datei betrifft, können Sie dies mit einer Kategorie tun, indem Sie die Kategorie am Ende der .m-Datei implementieren:

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

oder mit einer Klassenerweiterung (die Sache, die Sie als "leere Kategorie" bezeichnen), definieren Sie diese Methoden einfach zuletzt. Objective-C-Methoden können in der Implementierung in beliebiger Reihenfolge definiert und verwendet werden. Es hindert Sie also nichts daran, die "privaten" Methoden am Ende der Datei zu platzieren.

Selbst bei Klassenerweiterungen erstelle ich häufig einen separaten Header ( GLObject+Extension.h), damit ich diese Methoden bei Bedarf verwenden kann, um die Sichtbarkeit von "Freunden" oder "Geschützten" nachzuahmen.

Da diese Antwort ursprünglich geschrieben wurde, hat der Clang-Compiler begonnen, zwei Durchgänge für Objective-C-Methoden durchzuführen. Dies bedeutet, dass Sie vermeiden können, Ihre "privaten" Methoden vollständig zu deklarieren, und ob sie sich über oder unter der aufrufenden Site befinden, werden vom Compiler gefunden.


quelle
37

Obwohl ich kein Objective-C-Experte bin, definiere ich persönlich nur die Methode bei der Implementierung meiner Klasse. Zugegeben, es muss vor (oben) den Methoden definiert werden, die es aufrufen, aber es erfordert definitiv den geringsten Arbeitsaufwand.

Andy
quelle
4
Diese Lösung hat den Vorteil, dass überflüssige Programmstrukturen vermieden werden, um eine Compiler-Warnung zu vermeiden.
Jan Hettich
1
Ich mache das auch, bin aber auch kein Objective-C-Experte. Gibt es für die Experten einen Grund, dies nicht auf diese Weise zu tun (abgesehen von der Frage der Methodenbestellung)?
Eric Smith
2
Die Reihenfolge der Methoden scheint ein kleines Problem zu sein, aber wenn Sie sie in die Lesbarkeit von Code übersetzen, kann dies zu einem wichtigen Problem werden, insbesondere wenn Sie in einem Team arbeiten.
Borisdiakur
17
Die Reihenfolge der Methoden ist nicht mehr von Bedeutung. Neuere Versionen von LLVM kümmern sich nicht darum, in welcher Reihenfolge die Methoden implementiert werden. So können Sie sich auf die Bestellung einstellen, ohne vorher deklarieren zu müssen.
Steve Waddicor
Siehe auch diese Antwort von @justin
lagweezle
19

Das Definieren Ihrer privaten Methoden im @implementationBlock ist für die meisten Zwecke ideal. Clang sieht diese innerhalb der @implementation, unabhängig von der Reihenfolge der Deklaration. Es ist nicht erforderlich, sie in einer Klassenfortsetzung (auch als Klassenerweiterung bezeichnet) oder einer benannten Kategorie zu deklarieren.

In einigen Fällen müssen Sie die Methode in der Klassenfortsetzung deklarieren (z. B. wenn Sie den Selektor zwischen der Klassenfortsetzung und der verwenden @implementation).

static Funktionen sind sehr gut für besonders empfindliche oder geschwindigkeitskritische private Methoden.

Eine Konvention zum Benennen von Präfixen kann Ihnen dabei helfen, ein versehentliches Überschreiben privater Methoden zu vermeiden (ich finde den Klassennamen als Präfix sicher).

Benannte Kategorien (z. B. @interface MONObject (PrivateStuff)) sind wegen möglicher Namenskollisionen beim Laden keine besonders gute Idee. Sie sind wirklich nur für Freunde oder geschützte Methoden nützlich (die sehr selten eine gute Wahl sind). Um sicherzustellen, dass Sie vor unvollständigen Kategorieimplementierungen gewarnt werden, sollten Sie diese tatsächlich implementieren:

@implementation MONObject (PrivateStuff)
...HERE...
@end

Hier ist ein kleiner kommentierter Spickzettel:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

Ein weiterer Ansatz, der möglicherweise nicht offensichtlich ist: Ein C ++ - Typ kann sowohl sehr schnell sein als auch ein viel höheres Maß an Kontrolle bieten und gleichzeitig die Anzahl der exportierten und geladenen Objektmethoden minimieren.

Justin
quelle
1
+1 für die Verwendung des vollständigen Klassennamens als Präfix für den Methodennamen! Es ist viel sicherer als nur ein Unterstrich oder sogar Ihre eigene TLA. (Was ist, wenn sich die private Methode in einer Bibliothek befindet, die Sie in einem anderen Ihrer Projekte verwenden, und Sie vergessen, dass Sie den Namen bereits vor ein oder zwei Jahren verwendet haben ...?)
big_m
14

Sie können versuchen, eine statische Funktion unterhalb oder oberhalb Ihrer Implementierung zu definieren, die einen Zeiger auf Ihre Instanz verwendet. Es kann auf jede Ihrer Instanzvariablen zugreifen.

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end
Dreamlax
quelle
1
Apple hat Namen mit einem führenden Unterstrich für den eigenen Gebrauch reserviert.
Georg Schölly
1
Und was ist, wenn Sie die Frameworks von Apple nicht verwenden? Ich entwickle häufig Objective-C-Code ohne Apples Frameworks. Tatsächlich baue ich auf Linux, Windows und Mac OS X. Ich habe ihn trotzdem entfernt, da die meisten Leute, die in Objective-C codieren, ihn wahrscheinlich unter Mac OS X verwenden.
dreamlax
3
Ich denke, das ist wirklich eine private Methode in der .m-Datei. Andere Klassenkategoriemethoden sind eigentlich nicht privat, da Sie für Methoden im Block @interface ... @ end einfach keine privaten Methoden einfügen können.
David.Chu.ca
Warum würdest du das tun? Wenn Sie am Anfang der Methodendefinition einfach "-" hinzufügen, müssen Sie auf "self" zugreifen, ohne einen Parameter zu übergeben.
Guy
1
@Guy: denn dann ist die Methode durch Reflexion erkennbar und daher überhaupt nicht privat.
Dreamlax
3

Sie könnten Blöcke verwenden?

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

Ich bin mir bewusst, dass dies eine alte Frage ist, aber es ist eine der ersten, die ich gefunden habe, als ich nach einer Antwort auf diese Frage gesucht habe. Ich habe diese Lösung nirgendwo anders gesehen, also lassen Sie mich wissen, ob dies etwas Dummes ist.

FellowMD
quelle
1
Was Sie hier getan haben, ist das Erstellen einer globalen Blockvariablen, die nicht wirklich besser als eine Funktion ist (und nicht einmal wirklich privat, da sie nicht deklariert ist static). Aber ich habe mit dem Zuweisen von Blöcken zu privaten Ivars (nach der init-Methode) experimentiert - im JavaScript-Stil - was auch den Zugriff auf private Ivars ermöglicht, was mit statischen Funktionen nicht möglich ist. Ich bin mir noch nicht sicher, welches ich bevorzuge.
big_m
3

Alle Objekte in Objective C entsprechen dem NSObject-Protokoll, das die performSelector: -Methode enthält. Zuvor suchte ich auch nach einer Möglichkeit, einige "helfer- oder private" Methoden zu erstellen, die ich auf öffentlicher Ebene nicht offenlegen musste. Wenn Sie eine private Methode ohne Overhead erstellen möchten und diese nicht in Ihrer Header-Datei definieren müssen, probieren Sie dies aus ...

Definieren Sie Ihre Methode mit einer ähnlichen Signatur wie im folgenden Code ...

-(void)myHelperMethod: (id) sender{
     // code here...
}

Wenn Sie dann auf die Methode verweisen müssen, rufen Sie sie einfach als Selektor auf ...

[self performSelector:@selector(myHelperMethod:)];

Diese Codezeile ruft die von Ihnen erstellte Methode auf und enthält keine nervige Warnung, dass sie nicht in der Header-Datei definiert ist.

Zack Sheppard
quelle
6
Auf diese Weise haben Sie keine Möglichkeit, einen dritten Parameter zu übergeben.
Li Fumin
2

Wenn Sie den @interfaceBlock oben vermeiden möchten, können Sie die privaten Deklarationen immer in eine andere Datei MyClassPrivate.heinfügen, die nicht ideal ist, aber die Implementierung nicht überfüllt.

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end
rebellzach
quelle
2

Eine weitere Sache, die ich hier nicht gesehen habe - Xcode unterstützt .h-Dateien mit dem Namen "_private". Angenommen, Sie haben eine Klasse MyClass - Sie haben MyClass.m und MyClass.h und jetzt können Sie auch MyClass_private.h haben. Xcode erkennt dies und nimmt es in die Liste der "Gegenstücke" im Assistenten-Editor auf.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
Rich Schönthal
quelle
1

Es gibt keine Möglichkeit, das zweite Problem zu umgehen. So funktioniert der C-Compiler (und damit der Objective-C-Compiler). Wenn Sie den XCode-Editor verwenden, sollte das Funktions-Popup das Navigieren in den @interfaceund @implementation-Blöcken in der Datei erleichtern .

Barry Wark
quelle
1

Es gibt einen Vorteil der Abwesenheit privater Methoden. Sie können die Logik, die Sie ausblenden möchten, in die separate Klasse verschieben und als Delegat verwenden. In diesem Fall können Sie das delegierte Objekt als privat markieren und es ist von außen nicht sichtbar. Wenn Sie die Logik in die separate Klasse (möglicherweise mehrere) verschieben, können Sie Ihr Projekt besser gestalten. Weil Ihre Klassen einfacher werden und Ihre Methoden in Klassen mit Eigennamen gruppiert sind.

Sneg
quelle
0

Wie andere Leute sagten, ist das Definieren privater Methoden im @implementationBlock für die meisten Zwecke in Ordnung.

Zum Thema Code-Organisation - Ich halte sie gerne zusammen, um die pragma mark privateNavigation in Xcode zu vereinfachen

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Mailand
quelle