Propriétés en lecture seule dans Objective-C?

93

J'ai déclaré une propriété en lecture seule dans mon interface en tant que telle:

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

Peut-être que je ne comprends pas bien les propriétés, mais je pensais que lorsque vous le déclarez comme readonly, vous pouvez utiliser le setter généré dans le .mfichier implementation ( ), mais les entités externes ne peuvent pas changer la valeur. Cette question SO dit que c'est ce qui devrait arriver. C'est le comportement que je recherche. Cependant, lorsque vous essayez d'utiliser le setter standard ou la syntaxe dot pour définir à l' eventDomainintérieur de ma méthode init, cela me donne une unrecognized selector sent to instance.erreur. Bien sûr, je suis @synthesizela propriété. Essayer de l'utiliser comme ceci:

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

Alors ai-je mal compris la readonlydéclaration sur une propriété? Ou est-ce qu'il se passe autre chose?

Alex
la source

Réponses:

116

Vous devez dire au compilateur que vous voulez également un setter. Une manière courante consiste à le mettre dans une extension de classe dans le fichier .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Eiko
la source
22
Ce n'est pas une catégorie. C'est une extension de classe (comme l'a dit Eiko). Ils sont nettement différents, bien qu'ils soient utilisés à des fins similaires. Vous ne pouvez pas, par exemple, faire ce qui précède dans une vraie catégorie.
bbum
2
J'ai remarqué qu'une classe qui hérite d'une classe qui a des propriétés d'extension de classe ne les verra pas. c'est-à-dire l'héritage voir uniquement l'interface externe. confirmer?
Yogev Shelly
5
Ce n'est pas tout à fait juste bbum. Cela peut être différent, mais il est connu sous le nom de «catégorie anonyme»
MaxGabriel
3
Est-ce en plus de la déclaration initiale de l'op dans l'interface publique? Cela signifie que cette déclaration d'extension de classe remplace la déclaration initiale dans le .h? Sinon, je ne vois pas comment cela exposerait un setter public. Merci
Madbreaks
4
@Madbreaks C'est supplémentaire, mais c'est dans le fichier .m. Ainsi, le compilateur sait le rendre en lecture-écriture pour cette classe, mais l'utilisation externe est toujours limitée à lecture seule comme prévu.
Eiko
38

Eiko et d'autres ont donné des réponses correctes.

Voici un moyen plus simple: accédez directement à la variable de membre privé.

Exemple

Dans le fichier .h d'en-tête:

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

Dans le fichier .m d'implémentation:

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

Voilà, c'est tout ce dont vous avez besoin. Pas de chichi, pas de chichi.

Détails

À partir de Xcode 4.4 et LLVM Compiler 4.0 ( nouvelles fonctionnalités de Xcode 4.4 ), vous n'avez pas besoin de jouer avec les tâches abordées dans les autres réponses:

  • Le synthesizemot clé
  • Déclaration d'une variable
  • Re-déclarer la propriété dans le fichier .m d'implémentation.

Après avoir déclaré une propriété foo, vous pouvez supposer Xcode a ajouté une variable de membre privé nommé par un préfixe de underscore: _foo.

Si la propriété a été déclarée readwrite, Xcode génère une méthode getter nommée fooet un setter nommé setFoo. Ces méthodes sont appelées implicitement lorsque vous utilisez la notation par points (mon Object.myMethod). Si la propriété a été déclarée readonly, aucun setter n'est généré. Cela signifie que la variable de sauvegarde, nommée avec le trait de soulignement, n'est pas elle-même en lecture seule. Les readonlymoyens simplement qu'aucun mutateur a été synthétisé, et donc en utilisant la notation de points pour définir une valeur échoue avec une erreur du compilateur. La notation par points échoue car le compilateur vous empêche d'appeler une méthode (le setter) qui n'existe pas.

Le moyen le plus simple consiste à accéder directement à la variable membre, nommée avec le trait de soulignement. Vous pouvez le faire même sans déclarer cette variable nommée par soulignement! Xcode insère cette déclaration dans le cadre du processus de construction / compilation, donc votre code compilé aura effectivement la déclaration de variable. Mais vous ne voyez jamais cette déclaration dans votre fichier de code source d'origine. Pas de magie, juste du sucre syntaxique .

L'utilisation self->est un moyen d'accéder à une variable membre de l'objet / de l'instance. Vous pourrez peut-être omettre cela et utiliser simplement le nom var. Mais je préfère utiliser la flèche auto + car cela rend mon code auto-documenté. Lorsque vous voyez le, self->_foovous savez sans ambiguïté qu'il _foos'agit d'une variable membre sur cette instance.


Soit dit en passant, la discussion des avantages et des inconvénients des accesseurs de propriété par rapport à l' accès Ivar directe est exactement le genre de traitement réfléchi que vous allez lire dans le Dr Matt Neuberg de programmation iOS livre. J'ai trouvé très utile de lire et de relire.

Basil Bourque
la source
1
Peut-être devriez-vous ajouter qu'il ne faut jamais accéder directement à une variable membre (sans sa classe)! Cela constitue une violation de la POO (dissimulation d'informations).
HAS
2
@HAS Correct, le scénario ici définit en privé la variable membre non vue de l'extérieur de cette classe. C'est le point entier de la question de l'affiche d'origine: déclarer une propriété comme readonlytelle qu'aucune autre classe ne peut la définir.
Basil Bourque
Je regardais ce post pour savoir comment créer une propriété en lecture seule. Cela ne fonctionne pas pour moi. Cela me permet d'attribuer la valeur dans l'init, mais je peux également modifier la _variable plus tard avec une affectation. il ne déclenche pas d'erreur ou d'avertissement du compilateur.
netskink
@BasilBourque Merci beaucoup pour la modification utile sur cette question de "persistance"!
GhostCat
36

Une autre façon que j'ai trouvée de travailler avec les propriétés en lecture seule est d'utiliser @synthesize pour spécifier le magasin de stockage. Par exemple

@interface MyClass

@property (readonly) int whatever;

@end

Puis dans la mise en œuvre

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Vos méthodes peuvent ensuite être définies m_whatever, car il s'agit d'une variable membre.


Une autre chose intéressante que j'ai réalisée ces derniers jours est que vous pouvez créer des propriétés en lecture seule qui sont inscriptibles par des sous-classes comme celles-ci:

(dans le fichier d'en-tête)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Puis, dans la mise en œuvre

@synthesize myProperty = m_propertyBackingStore;

Il utilisera la déclaration dans le fichier d'en-tête, afin que les sous-classes puissent mettre à jour la valeur de la propriété, tout en conservant sa lecture seule.

Un peu malheureusement en termes de masquage et d'encapsulation des données.

yano
la source
1
La première manière que vous avez décrite est plus facile à traiter que la réponse acceptée. Juste un "@synthesize foo1, foo2, foo3;" dans le .m, et tout va bien.
sudo
20

Voir Personnalisation des classes existantes dans la documentation iOS.

readonly Indique que la propriété est en lecture seule. Si vous spécifiez en lecture seule, seule une méthode getter est requise dans @implementation. Si vous utilisez @synthesize dans le bloc d'implémentation, seule la méthode getter est synthétisée. De plus, si vous essayez d'attribuer une valeur à l'aide de la syntaxe dot, vous obtenez une erreur du compilateur.

Les propriétés en lecture seule n'ont qu'une méthode getter. Vous pouvez toujours définir l'ivar de support directement dans la classe de la propriété ou en utilisant le codage de valeur de clé.

Jonas
la source
9

Vous ne comprenez pas l'autre question. Dans cette question, il y a une extension de classe, déclarée ainsi:

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

C'est ce qui génère le setter uniquement visible dans l'implémentation de la classe. Ainsi, comme le dit Eiko, vous devez déclarer une extension de classe et remplacer la déclaration de propriété pour indiquer au compilateur de générer un setter uniquement dans la classe.

BoltClock
la source
5

La solution la plus courte est:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
user2159978
la source
2

Si une propriété est définie en lecture seule, cela signifie qu'il n'y aura effectivement pas de setter pouvant être utilisé soit en interne à la classe, soit en externe à partir d'autres classes. (c'est-à-dire: vous n'aurez un "getter" que si cela a du sens.)

D'après les sons de celui-ci, vous voulez une propriété de lecture / écriture normale qui est marquée comme privée, ce que vous pouvez obtenir en définissant la variable de classe comme privée dans votre fichier d'interface en tant que telle:

@private
    NSString* eventDomain;
}
John Parker
la source