web-dev-qa-db-ger.com

Deklaration / Definition von Variablenpositionen in Ziel C?

Seit ich angefangen habe, an iOS-Apps und Objective C zu arbeiten, war ich wirklich verwirrt über die verschiedenen Orte, an denen man Variablen deklarieren und definieren könnte. Auf der einen Seite haben wir den traditionellen C-Ansatz, auf der anderen Seite haben wir die neuen ObjectiveC-Direktiven, die OO) hinzufügen. Könnten Sie mir die besten Praktiken und Situationen erklären, in denen ich Möchten Sie diese Speicherorte für meine Variablen verwenden und möglicherweise mein derzeitiges Verständnis korrigieren?

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 dies dateibasierte Deklarationen und Definitionen im C-Stil sind, 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 Dient zuvor zum Implementieren von Singletons auf Basis statischer Variablen. Gibt es andere praktische Verwendungsmöglichkeiten, die ich vermisse?
  • Aus meiner Arbeit mit iOS geht hervor, 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 lassen sich ohne Deklaration in der Schnittstelle problemlos kompilieren. Ist es hauptsächlich für die Lesbarkeit?

Vielen Dank, Leute!

109

Ich kann deine Verwirrung verstehen. Insbesondere seit den letzten Aktualisierungen von Xcode und dem neuen LLVM-Compiler wurde die Art und Weise geändert, in der Ivars und Eigenschaften deklariert werden können.

Vor "modernem" Objective-C (in "altem" Obj-C 2.0) hatten Sie keine große Auswahl. Instanzvariablen wurden früher im Header zwischen den geschweiften Klammern deklariert { }:

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

Auf diese Variablen konnten Sie nur in Ihrer Implementierung zugreifen, nicht jedoch aus anderen Klassen. Dazu mussten Accessor-Methoden deklariert werden, 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 eckige Klammersyntax 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, wurden @property Und @synthesize Eingeführt, 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 viel klarerer und kürzerer Code. Die Zugriffsmethoden werden für Sie implementiert, und Sie können die Klammersyntax weiterhin wie zuvor verwenden. Sie können aber 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 @synthesize Überspringen. Wenn Sie kein ivar deklarieren, fügt der Compiler es für Sie hinzu und generiert auch die Accessormethoden, ohne dass Sie @synthesize Verwenden müssen.

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 Ivars mit @synthesize myVar = iVarName; Ändern.

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

// MyClass.m
@implementation MyClass
@end

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

Ein @interface - Block in der Implementierungsdatei ist eigentlich ein Extension und kann verwendet werden, um Deklarationsmethoden (nicht mehr benötigt) weiterzuleiten und Eigenschaften (neu) zu deklarieren. Sie können beispielsweise eine readonly -Eigenschaft in Ihrem Header deklarieren.

@property (nonatomic, readonly) myReadOnlyVar;

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

Was das Deklarieren von Variablen außerhalb eines @interface - oder @implementation - Blocks betrifft, so handelt es sich um einfache C-Variablen, die genau gleich funktionieren.

147
DrummerB

Lesen Sie zuerst die Antwort von @ DrummerB. Es gibt einen guten Überblick darüber, warum und was Sie generell tun sollten. In diesem Sinne zu Ihren spezifischen Fragen:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

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

  • typdefs
  • aufzählungen
  • externs

Externe sehen aus wie Variablendeklarationen, sind jedoch nur ein Versprechen, sie tatsächlich an einer anderen Stelle 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 Ihrem .m Datei deklariert die aktuelle Konstante:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

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

Wie DrummerB feststellt, ist dies ein Erbe. Stell hier nichts ab.


// 3) class-specific method / property declarations

@end

Ja.


#import "SampleClass.h"

// 4) what goes here?

Externe Konstanten wie oben beschrieben. Auch dateistatische Variablen können hier 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 es sich um eine ObjC++ - Klasse handelt) 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 nicht mehr @synthetisieren. 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. So entsteht Verwirrung aus der Einfachheit: D

41
Rob Napier

Ich bin auch ziemlich neu, also hoffe ich, dass ich nichts vermassle.

1 & 4: Globale Variablen im C-Stil: Sie haben einen großen Dateibereich. 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 übersichtlich 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 die in der öffentlichen Kopfzeile benötigten Elemente anzeigen möchten, Unterklassen sie jedoch nicht AFAIK erben.

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

5: Private Schnittstelle. Ich benutze immer private Schnittstellen, wenn 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 noch andere Gründe gibt.

6
Metabble

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 für keine andere Klasse sichtbar sind. Dies ist ein Sichtbarkeitsproblem. Wenn Sie sie also mit @property Oder @public Deklarieren, wird dies nicht geändert.

In einem Konstruktor empfiehlt es sich, auf Variablen zuzugreifen, die mit @property Deklariert wurden, und stattdessen den Unterstrich self zu verwenden, 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 das Backing IVAR _iNotVisible2 Zugreifen konnte, das für die Unterklasse privat ist. In Objective-C können alle Variablen gelesen oder gesetzt werden, auch die mit @private Gekennzeichneten, keine Ausnahmen.

Ich habe keine assoziierten Objekte oder C-Variablen eingefügt, da es sich um verschiedene Vögel handelt. Bei C-Variablen ist jede Variable, die außerhalb von @interface X{} Oder @implementation X{} Definiert ist, eine C-Variable mit Dateibereich und statischem Speicher.

Ich habe weder über Speicherverwaltungsattribute noch über Readonly/Readwrite-, Getter/Setter-Attribute gesprochen.

5
Jano