Schreibgeschützte Eigenschaften in Objective-C?

93

Ich habe eine schreibgeschützte Eigenschaft in meiner Schnittstelle als solche deklariert:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

Vielleicht verstehe ich Eigenschaften falsch, aber ich dachte, wenn Sie es als deklarieren readonly, können Sie den generierten Setter in der Implementierungsdatei ( .m) verwenden, aber externe Entitäten können den Wert nicht ändern. Diese SO-Frage besagt, dass genau das passieren sollte. Das ist das Verhalten, nach dem ich bin. Wenn eventDomainich jedoch versuche, die Standardsetz- oder Punktsyntax zum Festlegen innerhalb meiner init-Methode zu verwenden, wird ein unrecognized selector sent to instance.Fehler angezeigt. Natürlich bin ich auf @synthesizedem Grundstück. Der Versuch, es so zu benutzen:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

Verstehe ich also die readonlyErklärung zu einer Immobilie falsch ? Oder ist noch etwas los?

Alex
quelle

Antworten:

116

Sie müssen dem Compiler mitteilen, dass Sie auch einen Setter möchten. Eine übliche Methode besteht darin, es in eine Klassenerweiterung in der .m-Datei einzufügen:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Eiko
quelle
22
Es ist keine Kategorie. Es ist eine Klassenerweiterung (wie Eiko sagte). Sie unterscheiden sich deutlich, werden jedoch für ähnliche Zwecke verwendet. Sie können dies beispielsweise nicht in einer echten Kategorie tun.
bbum
2
Ich habe festgestellt, dass eine Klasse, die von einer Klasse mit Klassenerweiterungseigenschaften erbt, diese nicht sieht. dh Vererbung nur die externe Schnittstelle sehen. bestätigen?
Yogev Shelly
5
Das ist nicht ganz fair. Es mag anders sein, aber es ist bekannt als "Anonyme Kategorie"
MaxGabriel
3
Ist dies zusätzlich zu der ursprünglichen Deklaration der Operation in der öffentlichen Schnittstelle? Bedeutet dies, dass diese Klassenerweiterungsdeklaration die ursprüngliche Deklaration in überschreibt .h? Ansonsten sehe ich nicht, wie dies einen öffentlichen Setter entlarven würde. Danke
Madbreaks
4
@ Madbreaks Es ist zusätzlich, aber es ist in der .m-Datei. Der Compiler weiß also, dass er es für diese Klasse schreibgeschützt machen muss, aber die externe Verwendung ist weiterhin wie erwartet auf schreibgeschützt beschränkt.
Eiko
38

Eiko und andere gaben richtige Antworten.

Hier ist ein einfacher Weg: Greifen Sie direkt auf die private Mitgliedsvariable zu.

Beispiel

In der Header-H-Datei:

@property (strong, nonatomic, readonly) NSString* foo;

In der Implementierungsdatei .m:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

Das ist es, das ist alles was du brauchst. Kein Muss, keine Aufregung.

Einzelheiten

Ab Xcode 4.4 und LLVM Compiler 4.0 ( Neue Funktionen in Xcode 4.4 ) müssen Sie sich nicht mit den in den anderen Antworten beschriebenen Aufgaben herumschlagen:

  • Das synthesize Schlüsselwort
  • Eine Variable deklarieren
  • Deklarieren Sie die Eigenschaft in der .m-Datei der Implementierung erneut.

Nach dem Deklarieren einer Eigenschaft fookönnen Sie davon ausgehen, dass Xcode eine private Mitgliedsvariable mit dem Präfix Unterstrich hinzugefügt hat:_foo .

Wenn die Eigenschaft deklariert wurde readwrite, generiert Xcode eine Getter-Methode mit dem Namen foound einen Setter mit dem Namen setFoo. Diese Methoden werden implizit aufgerufen, wenn Sie die Punktnotation (my Object.myMethod) verwenden. Wenn die Eigenschaft deklariert wurde readonly, wird kein Setter generiert. Das bedeutet, dass die mit dem Unterstrich benannte Hintergrundvariable selbst nicht schreibgeschützt ist. Dies readonlybedeutet einfach, dass keine Setter-Methode synthetisiert wurde und daher die Verwendung der Punktnotation zum Setzen eines Werts mit einem Compilerfehler fehlschlägt. Die Punktnotation schlägt fehl, weil der Compiler Sie daran hindert, eine nicht vorhandene Methode (den Setter) aufzurufen.

Der einfachste Weg, dies zu umgehen, besteht darin, direkt auf die Mitgliedsvariable zuzugreifen, die mit dem Unterstrich benannt ist. Sie können dies auch tun, ohne diese Variable mit dem Unterstrich zu deklarieren! Xcode fügt diese Deklaration als Teil des Build / Compile-Prozesses ein, sodass Ihr kompilierter Code tatsächlich die Variablendeklaration enthält. Diese Deklaration wird jedoch nie in Ihrer ursprünglichen Quellcodedatei angezeigt. Keine Magie, nur syntaktischer Zucker .

Mit self->ist eine Möglichkeit, auf eine Mitgliedsvariable des Objekts / der Instanz zuzugreifen. Möglicherweise können Sie dies weglassen und einfach den Variablennamen verwenden. Ich bevorzuge jedoch die Verwendung des Pfeils self +, da sich mein Code dadurch selbst dokumentiert. Wenn Sie das sehen self->_foo, wissen Sie ohne Mehrdeutigkeit, dass dies _fooeine Mitgliedsvariable in dieser Instanz ist.


Durch die Art und Weise, die Diskussion über Vor- und Nachteile der Eigenschaftenaccessoren gegen direkten Ivar Zugang ist genau die Art von nachdenklicher Behandlung , die Sie in Dr. lesen werden Matt Neuberg ‚s Programming iOS Buch. Ich fand es sehr hilfreich zu lesen und erneut zu lesen.

Basil Bourque
quelle
1
Vielleicht sollten Sie hinzufügen, dass man niemals direkt auf eine Mitgliedsvariable zugreifen sollte (von ohne ihre Klasse)! Dies ist eine Verletzung von OOP (Information Hidden).
HAT
2
@HAS Richtig, das Szenario hier setzt die Mitgliedsvariable privat , die von außerhalb dieser Klasse nicht gesehen wird. Das ist der ganze Punkt der Frage des Originalplakats: Eine Eigenschaft readonlyso deklarieren , dass keine andere Klasse sie festlegen darf.
Basil Bourque
Ich habe in diesem Beitrag nachgeschaut, wie eine schreibgeschützte Eigenschaft erstellt wird. Das funktioniert bei mir nicht. Es erlaubt mir zwar, den Wert in der Init zuzuweisen, aber ich kann die _Variable später auch mit einer Zuweisung ändern. Es wird kein Compilerfehler oder eine Warnung ausgegeben.
Netskink
@BasilBourque Vielen Dank für die hilfreiche Bearbeitung dieser "Persistenz" -Frage!
GhostCat
36

Eine andere Möglichkeit, mit schreibgeschützten Eigenschaften zu arbeiten, besteht darin, @synthesize zu verwenden, um den Sicherungsspeicher anzugeben. Beispielsweise

@interface MyClass

@property (readonly) int whatever;

@end

Dann in der Implementierung

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Ihre Methoden können dann festgelegt werden m_whatever, da es sich um eine Mitgliedsvariable handelt.


Eine andere interessante Sache, die ich in den letzten Tagen erkannt habe, ist, dass Sie schreibgeschützte Eigenschaften erstellen können, die von Unterklassen wie diesen beschreibbar sind:

(in der Header-Datei)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Dann in der Implementierung

@synthesize myProperty = m_propertyBackingStore;

Die Deklaration wird in der Header-Datei verwendet, sodass Unterklassen den Wert der Eigenschaft unter Beibehaltung ihrer Lesbarkeit aktualisieren können.

Etwas bedauerlich in Bezug auf das Verstecken und die Kapselung von Daten.

Yano
quelle
1
Der erste Weg, den Sie beschrieben haben, ist einfacher zu handhaben als die akzeptierte Antwort. Nur ein "@synthesize foo1, foo2, foo3;" in der .m, und alles ist gut.
Sudo
20

Siehe Anpassen vorhandener Klassen in den iOS-Dokumenten.

schreibgeschützt Gibt an, dass die Eigenschaft schreibgeschützt ist. Wenn Sie schreibgeschützt angeben, ist in der @ -Implementierung nur eine Getter-Methode erforderlich. Wenn Sie im Implementierungsblock @synthesize verwenden, wird nur die Getter-Methode synthetisiert. Wenn Sie versuchen, einen Wert mithilfe der Punktsyntax zuzuweisen, wird außerdem ein Compilerfehler angezeigt.

Schreibgeschützte Eigenschaften haben nur eine Getter-Methode. Sie können den Hintergrund ivar weiterhin direkt in der Klasse der Eigenschaft oder mithilfe der Schlüsselwertcodierung festlegen.

Jona
quelle
8

Sie verstehen die andere Frage falsch. In dieser Frage gibt es eine Klassenerweiterung, die folgendermaßen deklariert ist:

@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

Dies erzeugt den Setter, der nur in der Implementierung der Klasse sichtbar ist. Wie Eiko sagt, müssen Sie eine Klassenerweiterung deklarieren und die Eigenschaftsdeklaration überschreiben, um den Compiler anzuweisen, nur innerhalb der Klasse einen Setter zu generieren.

BoltClock
quelle
5

Die kürzeste Lösung ist:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
user2159978
quelle
2

Wenn eine Eigenschaft als schreibgeschützt definiert ist, bedeutet dies, dass es effektiv keinen Setter gibt, der entweder intern für die Klasse oder extern von anderen Klassen verwendet werden kann. (dh: Sie werden nur dann einen "Getter" haben, wenn das Sinn macht.)

Von den Klängen her möchten Sie eine normale Lese- / Schreibeigenschaft, die als privat markiert ist. Dies können Sie erreichen, indem Sie die Klassenvariable in Ihrer Schnittstellendatei als solche als privat festlegen:

@private
    NSString* eventDomain;
}
John Parker
quelle