Propiedades de solo lectura en Objective-C?

93

He declarado una propiedad de solo lectura en mi interfaz como tal:

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

Tal vez no entiendo las propiedades, pero pensé que cuando lo declaras como readonly, puedes usar el setter generado dentro del .marchivo de implementación ( ), pero las entidades externas no pueden cambiar el valor. Esta pregunta SO dice que eso es lo que debería suceder. Ese es el comportamiento que busco. Sin embargo, cuando intento usar el setter estándar o la sintaxis de puntos para establecer eventDomaindentro de mi método init, me da un unrecognized selector sent to instance.error. Por supuesto que estoy @synthesizeen la propiedad. Intentando usarlo así:

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

Entonces, ¿estoy entendiendo mal la readonlydeclaración de una propiedad? ¿O está pasando algo más?

Alex
fuente

Respuestas:

116

Necesita decirle al compilador que también quiere un setter. Una forma común es ponerlo en una extensión de clase en el archivo .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Eiko
fuente
22
No es una categoría. Es una extensión de clase (como dijo Eiko). Son claramente diferentes, aunque se utilizan para fines similares. No podría, por ejemplo, hacer lo anterior en una categoría real.
bbum
2
Noté que una clase que hereda de una clase que tiene propiedades de extensión de clase, no las verá. es decir, la herencia solo ve la interfaz externa. ¿confirmar?
Yogev Shelly
5
Eso no es del todo justo. Puede ser diferente, pero se conoce como la "categoría anónima"
MaxGabriel
3
¿Es esto además de la declaración inicial de la operación en la interfaz pública? Es decir, ¿esta declaración de extensión de clase anula la declaración inicial en el .h? De lo contrario, no veo cómo esto expondría a un setter público. Gracias
Madbreaks
4
@Madbreaks Es adicional, pero está en el archivo .m. Entonces, el compilador sabe hacerlo de lectura y escritura para esa clase, pero el uso externo todavía está limitado a solo lectura como se esperaba.
Eiko
38

Eiko y otros dieron respuestas correctas.

He aquí una forma más sencilla: acceda directamente a la variable de miembro privado.

Ejemplo

En el archivo .h de encabezado:

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

En el archivo .m de implementación:

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

Eso es todo, eso es todo lo que necesitas. Sin despeinarse sin problemas.

Detalles

A partir de Xcode 4.4 y LLVM Compiler 4.0 ( Nuevas características en Xcode 4.4 ), no necesita meterse con las tareas discutidas en las otras respuestas:

  • La synthesizepalabra clave
  • Declarar una variable
  • Volver a declarar la propiedad en el archivo .m de implementación.

Después de declarar una propiedad foo, se puede asumir Xcode ha añadido una variable miembro privada llamada con un prefijo de subrayado: _foo.

Si la propiedad fue declarada readwrite, Xcode genera un método getter llamado fooy un setter llamado setFoo. Estos métodos se llaman implícitamente cuando usa la notación de puntos (my Object.myMethod). Si la propiedad fue declarada readonly, no se genera ningún setter. Eso significa que la variable de respaldo, nombrada con el guión bajo, no es de solo lectura. Esto readonlysignifica simplemente que no se sintetizó ningún método de establecimiento y, por lo tanto, el uso de la notación de puntos para establecer un valor falla con un error del compilador. La notación de puntos falla porque el compilador le impide llamar a un método (el establecedor) que no existe.

La forma más sencilla de evitar esto es acceder directamente a la variable miembro, nombrada con el guión bajo. ¡Puede hacerlo incluso sin declarar esa variable con el nombre de subrayado! Xcode está insertando esa declaración como parte del proceso de compilación / compilación, por lo que su código compilado tendrá la declaración de variable. Pero nunca ve esa declaración en su archivo de código fuente original. No magia, solo azúcar sintáctica .

El uso self->es una forma de acceder a una variable miembro del objeto / instancia. Es posible que pueda omitir eso y solo use el nombre var. Pero prefiero usar la flecha self + porque hace que mi código se autodocumente. Cuando ve el self->_foo, sabe sin ambigüedad que _fooes una variable miembro en esta instancia.


Por cierto, la discusión sobre los pros y los contras de los accesores de propiedad versus el acceso directo a ivar es exactamente el tipo de tratamiento reflexivo que leerá en Dr. Matt Neuberg 's Programación iOS libro. Me resultó muy útil leer y releer.

Albahaca Bourque
fuente
1
¡Quizás debería agregar que nunca se debe acceder a una variable miembro directamente (desde sin su clase)! Hacerlo es una violación de OOP (ocultación de información).
TIENE el
2
@HAS Correcto, el escenario aquí es establecer de forma privada la variable miembro que no se ve desde fuera de esta clase. Ese es el punto de la pregunta del cartel original: declarar una propiedad readonlypara que ninguna otra clase pueda establecerla.
Basil Bourque
Estaba mirando esta publicación sobre cómo crear una propiedad de solo lectura. Esto no funciona para mi. Me permite asignar el valor en el init, pero también puedo cambiar la variable _ más tarde con una asignación. no genera un error o advertencia del compilador.
netskink
@BasilBourque ¡Muchas gracias por la útil edición de esa pregunta de "persistencia"!
GhostCat
36

Otra forma que he encontrado para trabajar con propiedades de solo lectura es usar @synthesize para especificar el almacén de respaldo. Por ejemplo

@interface MyClass

@property (readonly) int whatever;

@end

Luego en la implementación

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Sus métodos pueden entonces establecerse m_whatever, ya que es una variable miembro.


Otra cosa interesante de la que me he dado cuenta en los últimos días es que puede hacer propiedades de solo lectura que sean escribibles por subclases como tales:

(en el archivo de encabezado)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Luego, en la implementación

@synthesize myProperty = m_propertyBackingStore;

Utilizará la declaración en el archivo de encabezado, por lo que las subclases pueden actualizar el valor de la propiedad, mientras conservan su solo lectura.

Sin embargo, un poco lamentablemente en términos de ocultación y encapsulación de datos.

yano
fuente
1
La primera forma que describiste es más fácil de manejar que la respuesta aceptada. Solo un "@synthesize foo1, foo2, foo3;" en el .m, y todo está bien.
sudo
20

Consulte Personalización de clases existentes en los documentos de iOS.

readonly Indica que la propiedad es de solo lectura. Si especifica readonly, solo se requiere un método getter en @implementation. Si usa @synthesize en el bloque de implementación, solo se sintetiza el método getter. Además, si intenta asignar un valor utilizando la sintaxis de puntos, obtendrá un error de compilación.

Las propiedades de solo lectura solo tienen un método getter. Aún puede configurar el ivar de respaldo directamente dentro de la clase de la propiedad o usando la codificación de valor clave.

Jonás
fuente
9

Está malinterpretando la otra pregunta. En esa pregunta hay una extensión de clase, declarada así:

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

Eso es lo que genera el setter solo visible dentro de la implementación de la clase. Entonces, como dice Eiko, debe declarar una extensión de clase y anular la declaración de propiedad para decirle al compilador que genere un establecedor solo dentro de la clase.

BoltClock
fuente
5

La solución más corta es:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
usuario2159978
fuente
2

Si una propiedad se define como de solo lectura, eso significa que efectivamente no habrá un definidor que pueda usarse internamente en la clase o externamente desde otras clases. (es decir, solo tendrá un "captador" si tiene sentido).

Por lo que parece, desea una propiedad de lectura / escritura normal que esté marcada como privada, lo que puede lograr configurando la variable de clase como privada en su archivo de interfaz como tal:

@private
    NSString* eventDomain;
}
John Parker
fuente