Was ist der Unterschied zwischen Ivar und Eigenschaften in Objective-C?

82

Was ist der semantische Unterschied zwischen diesen drei Arten der Verwendung von Ivars und Eigenschaften in Objective-C?

1.

@class MyOtherObject; 
@interface MyObject {
}
@property (nonatomic, retain) MyOtherObject *otherObj;

2.

#import "MyOtherObject.h"
@interface MyObject {
    MyOtherObject *otherObj;
}
@property (nonatomic, retain) MyOtherObject *otherObj;

3.

#import "MyOtherObject.h"
@interface MyObject {
    MyOtherObject *otherObj;
}
ennuikiller
quelle

Antworten:

57

Nummer 1 unterscheidet sich von den beiden anderen dadurch, dass die MyOtherObject-Klasse vorwärts deklariert wird, um die vom Compiler und Linker gesehene Codemenge zu minimieren und möglicherweise auch Zirkelverweise zu vermeiden. Wenn Sie dies auf diese Weise tun, denken Sie daran, den #import in die .m-Datei einzufügen.

Indem Sie eine @ -Eigenschaft deklarieren (und @synthesize in der .m-Datei abgleichen), generieren Sie automatisch Zugriffsmethoden mit der von Ihnen angegebenen Speichersemantik. Die Faustregel für die meisten Objekte lautet Beibehalten, aber NSStrings sollte beispielsweise Kopieren verwenden. Während Singletons und Delegierte normalerweise Assign verwenden sollten. Handschriftliche Accessoren sind mühsam und fehleranfällig, so dass viele Tipp- und dumme Fehler vermieden werden.

Wenn Sie eine synthetisierte Eigenschaft deklarieren, können Sie eine Accessor-Methode mit der folgenden Punktnotation aufrufen:

self.otherObj = someOtherNewObject; // set it  
MyOtherObject *thingee = self.otherObj; // get it 

Anstelle der normalen Art der Nachrichtenübermittlung:

[self setOtherObject:someOtherNewObject]; // set it
MyOtherObject *thingee = [self otherObj]; // get it 

Hinter den Kulissen rufen Sie wirklich eine Methode auf, die so aussieht:

- (void) setOtherObj:(MyOtherObject *)anOtherObject {

    if (otherObject == anOtherObject) {
        return;  
    }

    MyOtherObject *oldOtherObject = otherObject; // keep a reference to the old value for a second
    otherObject = [anOtherObject retain]; // put the new value in  
    [oldOtherObject release]; // let go of the old object
} // set it

…oder dieses

- (MyOtherObject *) otherObject {  
    return otherObject;
} // get it

Totaler Schmerz im Hintern, richtig. Tun Sie das jetzt für jeden Ivar in der Klasse. Wenn Sie es nicht genau richtig machen, kommt es zu einem Speicherverlust. Am besten lassen Sie den Compiler die Arbeit machen.

Ich sehe, dass Nummer 1 keinen Ivar hat. Angenommen, dies ist kein Tippfehler, ist es in Ordnung, da die Direktiven @property / @synthesize hinter den Kulissen auch für Sie einen Ivar deklarieren. Ich glaube, das ist neu für Mac OS X - Snow Leopard und iOS4.

In Nummer 3 werden diese Accessoren nicht generiert, sodass Sie sie selbst schreiben müssen. Wenn Sie möchten, dass Ihre Accessor-Methoden Nebenwirkungen haben, führen Sie Ihren Standard-Tanz zur Speicherverwaltung wie oben gezeigt durch und führen dann innerhalb der Accessor-Methode die erforderlichen Nebenarbeiten aus. Wenn Sie eine Eigenschaft synthetisieren und eine eigene schreiben , hat Ihre Version Vorrang.

Habe ich alles abgedeckt?

willc2
quelle
ja, vielen Dank! Eine Anmerkung, die ich machen möchte, ist, dass wenn Sie das Pragma der Vorwärtsklasse in # 1 herausnehmen und durch ein #import "MyOtherObject" ersetzen, Sie einen Fehler bei der Kompilierungszeit erhalten, nicht sicher warum ...
ennuikiller
Gibt es einen Vorteil bei der Verwendung von Ansatz 2 gegenüber Ansatz 1?
Greg
Die @ Greg-Methode Nr. 1 verhindert einen Zirkelverweis. Siehe stackoverflow.com/questions/7221174/…
willc2
3
Schöne Antwort, bis auf die Punktnotation. Sie müssen die Eigenschaft nicht synthetisieren, um sie für die Punktnotation zu verwenden. Tatsächlich müssen Sie überhaupt keine Eigenschaft deklarieren. Solange Sie einen deklarierten Setter und Getter (z. B. setFoo:und foo) haben, können Sie die Punktnotation verwenden.
JeremyP
Aus Gründen der Relevanz wird bei Verwendung von ARC die Synthese automatisch durchgeführt.
Sean Larkin
17

Früher hatten Sie Ivars, und wenn Sie eine andere Klasse einstellen oder lesen lassen wollten, mussten Sie einen Getter (dh -(NSString *)foo)einen Setter (dh -(void)setFoo:(NSString *)aFoo;) definieren.

Was Ihnen Eigenschaften geben, ist der Setter und Getter kostenlos (fast!) Zusammen mit einem Ivar. Wenn Sie jetzt eine Eigenschaft definieren, können Sie die Atomizität festlegen (möchten Sie beispielsweise mehrere Einstellungsaktionen von mehreren Threads zulassen) sowie die Semantik zuweisen / beibehalten / kopieren (dh sollte der Setter den neuen Wert kopieren) oder speichern Sie einfach den aktuellen Wert - wichtig, wenn eine andere Klasse versucht, Ihre Zeichenfolgeeigenschaft mit einer veränderlichen Zeichenfolge festzulegen, die später möglicherweise geändert wird.

Das @synthesizemacht es. Viele Leute lassen den ivar-Namen gleich, aber Sie können ihn ändern, wenn Sie Ihre Synthesize-Anweisung schreiben (dh, Sie @synthesize foo=_foo;erstellen einen ivar-Namen _foofür die Eigenschaft foo. Wenn Sie diese Eigenschaft also lesen oder schreiben möchten und sie nicht verwenden self.foo, werden Sie dies tun müssen verwenden _foo = ...- es hilft Ihnen nur, direkte Verweise auf den Ivar zu finden, wenn Sie nur durch den Setter und Getter gehen wollten).

Ab Xcode 4.6 müssen Sie die @synthesizeAnweisung nicht mehr verwenden - der Compiler führt dies automatisch aus und stellt standardmäßig den Namen des ivar voran _.

David H.
quelle
1
Es ist zu beachten, dass die Atomizität einer Eigenschaft keine Gewindesicherheit garantiert .
Jscs
Wenn ich also einen ivar habe, der atomar ist, meinst du damit, dass, während der Setter ihn einstellt oder der Getter ihn bekommt, ein anderer Thread einschaltet und versucht, beides zu tun, dass alles schadet? Was ist dann der Sinn von Atomic? Mein Verständnis ist, dass Atomic zumindest sicherstellt, dass, wenn Sie einen Ivar setzen, dieser gesetzt wird, seine Retentionszahl angemessen ist usw. Warum sonst Atomic? [Nicht, dass es alle Probleme löst, sondern nur verhindert, dass Sie sich schämen]
David H
2
Sie erhalten garantiert ein gültiges, ganzes Objekt - der Getter gibt kein Objekt zurück, das gerade freigegeben wird. Wenn jedoch ein anderer Thread den Setter verwendet, erhalten Sie möglicherweise den Wert von vorher oder nachher. Festlegen, dass dies außerhalb der Getter und Setter gehandhabt werden muss. Mit anderen Worten, während der Getter- oder Setter-Operation wird kein Thread unterbrochen , aber die Reihenfolge der Operationen ist nicht definiert (kann auf dieser Ebene nicht AFAIK sein).
Jscs
Nun, ich würde argumentieren, dass Ihr ursprünglicher Kommentar falsch platziert wurde - die Atomizität wird gewürdigt, es ist nur so, dass der Zugriff über Threads zu einer Reihe von Problemen führen kann -, daher ist jeder Ivar, den ich jemals deklariert habe, atomar und wenn es sich um Threads handelt, dann um Parallelität wird anderswo behandelt.
David H