Deklaration / Definition von Variablenpositionen in ObjectiveC?

112

Seit ich mit iOS-Apps und Ziel C arbeite, bin ich wirklich verwirrt über die verschiedenen Orte, an denen man Variablen deklarieren und definieren könnte. Einerseits haben wir den traditionellen C-Ansatz, andererseits haben wir die neuen ObjectiveC-Richtlinien, die zusätzlich OO hinzufügen. Könnten Sie mir helfen, die besten Praktiken und Situationen zu verstehen, in denen ich diese Speicherorte für meine Variablen verwenden und möglicherweise mein derzeitiges Verständnis korrigieren möchte?

Hier ist eine Beispielklasse (.h und .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

und

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • Mein Verständnis von 1 und 4 ist, dass es sich um dateibasierte Deklarationen und Definitionen im C-Stil handelt, die keinerlei Verständnis für das Konzept der Klasse haben und daher genau so verwendet werden müssen, wie sie in C verwendet würden. Ich habe sie gesehen wird zuvor für die Implementierung statischer variablenbasierter Singletons verwendet. Gibt es andere bequeme Verwendungszwecke, die mir fehlen?
  • Ich gehe davon aus, dass Ivars außerhalb der @synthesize-Direktive fast vollständig auslaufen und daher größtenteils ignoriert werden können. Ist das der Fall?
  • Zu 5: Warum sollte ich jemals Methoden in privaten Schnittstellen deklarieren wollen? Meine privaten Klassenmethoden scheinen ohne Deklaration in der Schnittstelle einwandfrei zu kompilieren. Ist es hauptsächlich für die Lesbarkeit?

Vielen Dank, Leute!

Alexandr Kurilin
quelle

Antworten:

153

Ich kann deine Verwirrung verstehen. Zumal die jüngsten Aktualisierungen von Xcode und des neuen LLVM-Compilers die Art und Weise geändert haben, wie Ivars und Eigenschaften deklariert werden können.

Vor "modernem" Objective-C (in "altem" Obj-C 2.0) hatten Sie nicht viele Möglichkeiten. Instanzvariablen wurden früher in der Kopfzeile zwischen den geschweiften Klammern deklariert { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Sie konnten nur in Ihrer Implementierung auf diese Variablen zugreifen, nicht jedoch aus anderen Klassen. Dazu mussten Sie Zugriffsmethoden deklarieren, die ungefähr so ​​aussehen:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

Auf diese Weise konnten Sie diese Instanzvariable auch von anderen Klassen abrufen und festlegen, indem Sie die übliche Syntax in eckigen Klammern zum Senden von Nachrichten verwendeten (Aufrufmethoden):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Da das manuelle Deklarieren und Implementieren jeder Zugriffsmethode ziemlich ärgerlich war @propertyund @synthesizeeingeführt wurde, um die Zugriffsmethoden automatisch zu generieren:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

Das Ergebnis ist ein viel klarerer und kürzerer Code. Die Zugriffsmethoden werden für Sie implementiert, und Sie können die Klammer-Syntax weiterhin wie zuvor verwenden. Darüber hinaus können Sie auch die Punktsyntax verwenden, um auf Eigenschaften zuzugreifen:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Seit Xcode 4.4 müssen Sie keine Instanzvariable mehr selbst deklarieren und können auch überspringen @synthesize. Wenn Sie kein ivar deklarieren, fügt der Compiler es für Sie hinzu und generiert auch die Zugriffsmethoden, ohne dass Sie es verwenden müssen @synthesize.

Der Standardname für das automatisch generierte ivar ist der Name oder Ihre Eigenschaft, beginnend mit einem Unterstrich. Sie können den Namen des generierten ivar mithilfe von ändern@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

Dies funktioniert genau wie der obige Code. Aus Kompatibilitätsgründen können Sie ivars weiterhin in der Kopfzeile deklarieren. Da der einzige Grund, warum Sie dies tun möchten (und keine Eigenschaft deklarieren möchten), darin besteht, eine private Variable zu erstellen, können Sie dies jetzt auch in der Implementierungsdatei tun. Dies ist der bevorzugte Weg.

Ein @interfaceBlock in der Implementierungsdatei ist eigentlich eine Erweiterung und kann verwendet werden, um Deklarationsmethoden weiterzuleiten (nicht mehr benötigt) und Eigenschaften (erneut) zu deklarieren. Sie können beispielsweise eine readonlyEigenschaft in Ihrem Header deklarieren .

@property (nonatomic, readonly) myReadOnlyVar;

und deklarieren Sie es erneut in Ihrer Implementierungsdatei readwrite, um es mithilfe der Eigenschaftssyntax und nicht nur über den direkten Zugriff auf das ivar festlegen zu können.

Wenn Sie Variablen vollständig außerhalb von any @interfaceoder @implementationblock deklarieren , handelt es sich um einfache C-Variablen, die genau gleich funktionieren.

SchlagzeugerB
quelle
2
gute Antwort! Beachten Sie auch: stackoverflow.com/questions/9859719/…
Nycynik
43

Lesen Sie zuerst die Antwort von @ DrummerB. Es ist ein guter Überblick über das Warum und was Sie im Allgemeinen tun sollten. In diesem Sinne zu Ihren spezifischen Fragen:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Hier finden Sie keine tatsächlichen Variablendefinitionen (dies ist technisch zulässig, wenn Sie genau wissen, was Sie tun, dies jedoch niemals tun). Sie können verschiedene andere Arten von Dingen definieren:

  • typdefs
  • Aufzählungen
  • externs

Externe sehen aus wie Variablendeklarationen, aber sie sind nur ein Versprechen, sie tatsächlich woanders zu deklarieren. In ObjC sollten sie nur zum Deklarieren von Konstanten und im Allgemeinen nur von Zeichenfolgenkonstanten verwendet werden. Zum Beispiel:

extern NSString * const MYSomethingHappenedNotification;

Sie würden dann in Ihrer .mDatei die tatsächliche Konstante deklarieren:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Wie von DrummerB festgestellt, ist dies ein Vermächtnis. Setzen Sie hier nichts ein.


// 3) class-specific method / property declarations

@end

Ja.


#import "SampleClass.h"

// 4) what goes here?

Externe Konstanten wie oben beschrieben. Hier können auch statische Dateivariablen abgelegt werden. Dies entspricht Klassenvariablen in anderen Sprachen.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

Ja


@implementation SampleClass
{
    // 6) define ivars
}

Aber sehr selten. Fast immer sollten Sie clang (Xcode) erlauben, die Variablen für Sie zu erstellen. Die Ausnahmen betreffen normalerweise Nicht-ObjC-Ivars (wie Core Foundation-Objekte und insbesondere C ++ - Objekte, wenn dies eine ObjC ++ - Klasse ist) oder Ivars mit seltsamer Speichersemantik (wie Ivars, die aus irgendeinem Grund nicht mit einer Eigenschaft übereinstimmen).


// 7) define methods and synthesize properties from both public and private
//    interfaces

Im Allgemeinen sollten Sie @synthesize nicht mehr verwenden. Clang (Xcode) erledigt das für Sie und Sie sollten es zulassen.

In den letzten Jahren sind die Dinge dramatisch einfacher geworden. Der Nebeneffekt ist, dass es jetzt drei verschiedene Epochen gibt (Fragile ABI, Non-Fragile ABI, Non-Fragile ABI + Auto-Syntheisze). Wenn Sie also den älteren Code sehen, kann dies etwas verwirrend sein. Verwirrung aufgrund der Einfachheit: D.

Rob Napier
quelle
Ich frage mich nur, aber warum sollten wir nicht explizit synthetisieren? Ich mache das, weil ich meinen Code leichter verständlich finde, insbesondere wenn einige Eigenschaften Accessoren synthetisiert haben und andere benutzerdefinierte Implementierungen haben, da ich an das Synthetisieren gewöhnt bin. Gibt es Nachteile bei der expliziten Synthese?
Metabble
Das Problem bei der Verwendung als Dokumentation ist, dass es nichts wirklich dokumentiert. Trotz der Verwendung von Synthetize haben Sie möglicherweise einen oder beide Accessoren überschrieben. An der Syntheser-Zeile kann man nichts wirklich Nützliches erkennen. Das einzige, was schlimmer ist als keine Dokumentation, ist eine irreführende Dokumentation. Lass es aus.
Rob Napier
3
Warum ist # 6 selten? Ist dies nicht der einfachste Weg, eine private Variable zu erhalten?
pfrank
Der einfachste und beste Weg, um ein Privateigentum zu bekommen, ist # 5.
Rob Napier
1
@ RobNapier Es ist immer noch notwendig, @ synthetize manchmal zu verwenden (z. B. wenn eine Eigenschaft schreibgeschützt ist und der Accessor überschrieben wird)
Andy
6

Ich bin auch ziemlich neu, also vermassle ich hoffentlich nichts.

1 & 4: Globale Variablen im C-Stil: Sie haben einen dateiweiten Bereich. Der Unterschied zwischen den beiden besteht darin, dass die erste Datei für jeden verfügbar ist, der den Header importiert, während die zweite nicht.

2: Instanzvariablen. Die meisten Instanzvariablen werden mithilfe von Eigenschaften über Zugriffsmethoden synthetisiert und abgerufen / festgelegt, da dies die Speicherverwaltung angenehm und einfach macht und Ihnen eine leicht verständliche Punktnotation bietet.

6: Implementierungs-Ivars sind etwas neu. Es ist ein guter Ort, um private Ivars zu platzieren, da Sie nur das offenlegen möchten, was im öffentlichen Header benötigt wird, aber Unterklassen sie nicht AFAIK erben.

3 & 7: Öffentliche Methoden- und Eigenschaftsdeklarationen, dann Implementierungen.

5: Private Schnittstelle. Ich benutze immer private Schnittstellen, wann immer ich kann, um die Dinge sauber zu halten und eine Art Black-Box-Effekt zu erzeugen. Wenn sie nichts davon wissen müssen, legen Sie es dort ab. Ich mache es auch aus Gründen der Lesbarkeit, weiß nicht, ob es andere Gründe gibt.

Metabble
quelle
1
Denken Sie nicht, dass Sie irgendetwas vermasselt haben :) Ein paar Kommentare - # 1 & # 4 besonders mit # 4 sehen Sie oft statische Speichervariablen. Bei Nummer 1 wird häufig der externe Speicher angegeben und dann der tatsächliche Speicher in Nummer 4 zugewiesen. # 2) normalerweise nur, wenn eine Unterklasse es aus irgendeinem Grund benötigt. # 5 nicht mehr erforderlich, um private Methoden weiterzuleiten.
Carl Veazey
Ja, ich habe gerade die Vorwärtserklärung selbst überprüft. Früher gab es eine Warnung, wenn eine private Methode eine andere aufrief, die danach ohne Vorwärtsdeklaration definiert wurde, oder? Ich war etwas überrascht, als es mich nicht warnte.
Metabble
Ja, es ist ein neuer Teil des Compilers. Sie haben in letzter Zeit wirklich viele Fortschritte gemacht.
Carl Veazey
5

Dies ist ein Beispiel für alle Arten von Variablen, die in Objective-C deklariert wurden. Der Variablenname gibt den Zugriff an.

Datei: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

Datei: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Beachten Sie, dass die iNotVisible-Variablen von keiner anderen Klasse aus sichtbar sind. Dies ist ein Sichtbarkeitsproblem, daher wird es deklariert @propertyoder @publicnicht geändert.

In einem Konstruktor empfiehlt es sich, auf Variablen zuzugreifen, die mit @propertyUnterstrichen deklariert wurden self, um Nebenwirkungen zu vermeiden.

Versuchen wir, auf die Variablen zuzugreifen.

Datei: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

Datei: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Wir können weiterhin über die Laufzeit auf die nicht sichtbaren Variablen zugreifen.

Datei: Cow.m (Teil 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Versuchen wir, auf die nicht sichtbaren Variablen zuzugreifen.

Datei: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Dies wird gedruckt

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Beachten Sie, dass ich auf den Hintergrund ivar zugreifen konnte, der _iNotVisible2für die Unterklasse privat ist. In Objective-C können alle Variablen gelesen oder gesetzt werden, auch die markierten @private, ohne Ausnahmen.

Ich habe keine zugehörigen Objekte oder C-Variablen aufgenommen, da es sich um verschiedene Vögel handelt. Wie bei C-Variablen ist jede Variable, die außerhalb von definiert ist @interface X{}oder @implementation X{}eine C-Variable mit Dateibereich und statischem Speicher ist.

Ich habe weder über Speicherverwaltungsattribute noch über schreibgeschützte / readwrite-, Getter- / Setter-Attribute gesprochen.

Jano
quelle