¿Cómo declaro propiedades de nivel de clase en Objective-C?

205

Tal vez esto sea obvio, pero no sé cómo declarar las propiedades de clase en Objective-C.

Necesito almacenar en caché un diccionario por clase y preguntarme cómo ponerlo en la clase

mamcx
fuente

Respuestas:

190

Qué propiedades tienen un significado específico en Objective-C, pero creo que quieres decir algo que es equivalente a una variable estática? Por ejemplo, ¿solo una instancia para todos los tipos de Foo?

Para declarar funciones de clase en Objective-C, usa el prefijo + en lugar de - para que su implementación se vea así:

// Foo.h
@interface Foo {
}

+ (NSDictionary *)dictionary;

// Foo.m
+ (NSDictionary *)dictionary {
  static NSDictionary *fooDict = nil;
  if (fooDict == nil) {
    // create dict
  }
  return fooDict;
}
Andrew Grant
fuente
23
¿Es esto correcto? ¿No establecerá fooDict en nulo ya que la primera línea del método del diccionario siempre da como resultado que el diccionario se vuelva a crear cada vez?
PapillonUK
59
La línea estática NSDictionary * fooDict = nil; ¡solo se ejecutará una vez! Incluso en un método llamado varias veces, la declaración (y también en este ejemplo la inicialización) con la palabra clave static se ignorará si existe una variable estática con este nombre.
Binario
3
@ BenC.R.Leggiero Sí, absolutamente. La .sintaxis de -accessor no está vinculada a las propiedades en Objective-C, es solo un acceso directo compilado para cualquier método que devuelva algo sin tomar ningún argumento. En este caso, lo preferiría: personalmente prefiero la .sintaxis para cualquier uso en el que el código del cliente intente obtener algo, no realizar una acción (incluso si el código de implementación puede crear algo una vez o realizar acciones de efectos secundarios) . La .sintaxis de uso intensivo también da como resultado un código más legible: la presencia de […]s significa que se está haciendo algo significativo cuando las recuperaciones usan la .sintaxis en su lugar.
Slipp D. Thompson,
44
Eche un vistazo a la respuesta de Alex Nolasco, las propiedades de clase están disponibles desde el lanzamiento de Xcode 8: stackoverflow.com/a/37849467/6666611
n3wbie
113

Estoy usando esta solución:

@interface Model
+ (int) value;
+ (void) setValue:(int)val;
@end

@implementation Model
static int value;
+ (int) value
{ @synchronized(self) { return value; } }
+ (void) setValue:(int)val
{ @synchronized(self) { value = val; } }
@end

Y lo encontré extremadamente útil como reemplazo del patrón Singleton.

Para usarlo, simplemente acceda a sus datos con notación de puntos:

Model.value = 1;
NSLog(@"%d = value", Model.value);
Spooki
fuente
3
Eso es realmente genial. Pero, ¿qué selfsignifica dentro de un método de clase?
Todd Lehman el
8
@ToddLehman selfes el objeto que recibió el mensaje . Y como las clases también son objetos , en este caso selfsignificaModel
spooki
66
¿Por qué tendría que ser el captador @synchronized?
Matt Kantor
1
Esto es realmente genial que esto funcione. Que puede escribir sus propias propiedades de nivel de clase que funcionan exactamente como las reales. Supongo que sincronizar self es el equivalente a usar 'atomic' en su declaración de propiedad y puede omitirse si desea la versión 'no atómica'. También consideraría nombrar las variables de respaldo con '_' como es predeterminado por apple y también mejora la legibilidad ya que devolver / configurar self.value del getter / setter causa una recursión infinita. En mi opinión, esta es la respuesta correcta.
Peter Segerblom
3
Es genial, pero ... ¿10 líneas de código solo para crear 1 miembro estático? que hack Apple debería hacer de esto una característica.
John Henckel
92

Como se ve en WWDC 2016 / XCode 8 ( novedades en la sesión LLVM @ 5: 05). Las propiedades de clase se pueden declarar de la siguiente manera

@interface MyType : NSObject
@property (class) NSString *someString;
@end

NSLog(@"format string %@", MyType.someString);

Tenga en cuenta que las propiedades de clase nunca se sintetizan

@implementation
static NSString * _someString;
+ (NSString *)someString { return _someString; }
+ (void)setSomeString:(NSString *)newString { _someString = newString; }
@end
Alex Nolasco
fuente
8
Quizás debería hacerse explícito que esto es solo azúcar para las declaraciones de accesor. Como notó, la propiedad no se sintetiza: una staticvariable ( ) aún debe declararse y usarse, y los métodos deben implementarse explícitamente, tal como estaban anteriormente. La sintaxis de puntos ya funcionaba anteriormente también. En general, esto suena como un problema mayor de lo que realmente es.
jscs
66
La gran cosa es que esto significa que puede acceder a singletons desde el código Swift sin usar (), y el sufijo de tipo se elimina por convención. Por ejemplo, XYZMyClass.shared (Swift 3) en lugar de XYZMyClass.sharedMyClass ()
Ryan
Esto no parece seguro para subprocesos. Si esto pudiera ser mutado de diferentes hilos en su código, me aseguraría de que maneje la posible condición de carrera que esto crearía.
smileBot
Una interfaz agradable y limpia para consumir clases, pero todavía es un montón de trabajo. Intenté esto con un bloque estático y no fue muy divertido. De hecho, solo usar la estática fue mucho más fácil.
Departamento B
1
la implementación se puede mejorar fácilmente para un comportamiento "singleton" seguro para subprocesos, utilizando el token dispatch_once, pero de lo contrario la solución demuestra correctamente la respuesta
Motti Shneor
63

Si está buscando el equivalente a nivel de clase de @property, entonces la respuesta es "no existe tal cosa". Pero recuerde @property, de todos modos , es solo azúcar sintáctico; solo crea métodos de objeto con nombres apropiados.

Desea crear métodos de clase que accedan a variables estáticas que, como han dicho otros, solo tienen una sintaxis ligeramente diferente.

Jim Puls
fuente
Incluso las propiedades de pensamiento son sintácticas. Sería bueno poder usar la sintaxis de puntos para cosas como MyClass.class en lugar de [MyClass class].
Zaky German
44
@ZakyGerman ¡Puedes! UIDevice.currentDevice.identifierForVendorfunciona para mi.
tc.
1
@tc. ¡Gracias! Llenando tonto ahora. Por alguna razón, estaba convencido de que lo intenté en el pasado pero no funcionó. ¿Es esa una característica nueva por casualidad?
Zaky German
1
@ZakyGerman Ha funcionado durante al menos un año o dos para los métodos de clase. Creo que siempre ha funcionado, por ejemplo, los métodos si los métodos getter / setter tienen los tipos esperados.
tc.
21

Aquí hay una manera segura de hacerlo:

// Foo.h
@interface Foo {
}

+(NSDictionary*) dictionary;

// Foo.m
+(NSDictionary*) dictionary
{
  static NSDictionary* fooDict = nil;

  static dispatch_once_t oncePredicate;

  dispatch_once(&oncePredicate, ^{
        // create dict
    });

  return fooDict;
}

Estas ediciones aseguran que fooDict solo se cree una vez.

De la documentación de Apple : "dispatch_once: ejecuta un objeto de bloque una vez y solo una vez durante la vida útil de una aplicación".

Quentin
fuente
3
¿No es irrelevante el código dispatch_once ya que un NSDictionary estático podría inicializarse en la primera línea del método de diccionario + (NSDictionary *) y dado que es estático, solo se inicializará una vez?
jcpennypincher
@jcpennypincher intentar inicializar el diccionario en la misma línea que su declaración estática produce el siguiente error de compilación: Initializer element is not a compile-time constant.
George WS
@GeorgeWS Solo obtiene ese error porque está intentando inicializarlo como resultado de una función (alloc e init son funciones). Si lo inicializa en nil, y luego agrega if (obj == nil) e inicializa allí, estará bien.
Rob
1
Rob, eso no es seguro para subprocesos. El código presentado aquí es el mejor.
Ian Ollmann
Me gusta más esta respuesta, porque es completa y segura para subprocesos; sin embargo, solo trata mejor con la implementación, mientras que la pregunta del operador era sobre la declaración de las propiedades de clase, no su implementación (que no tiene nada que ver con la implementación). Gracias de cualquier manera.
Motti Shneor
11

A partir de Xcode 8, Objective-C ahora admite propiedades de clase:

@interface MyClass : NSObject
@property (class, nonatomic, assign, readonly) NSUUID* identifier;
@end

Como las propiedades de clase nunca se sintetizan, debe escribir su propia implementación.

@implementation MyClass
static NSUUID*_identifier = nil;

+ (NSUUID *)identifier {
  if (_identifier == nil) {
    _identifier = [[NSUUID alloc] init];
  }
  return _identifier;
}
@end

Accede a las propiedades de la clase utilizando la sintaxis de punto normal en el nombre de la clase:

MyClass.identifier;
Berbie
fuente
7

Las propiedades tienen valores solo en objetos, no en clases.

Si necesita almacenar algo para todos los objetos de una clase, debe usar una variable global. Puede ocultarlo declarándolo staticen el archivo de implementación.

También puede considerar el uso de relaciones específicas entre sus objetos: atribuye un rol de maestro a un objeto específico de su clase y vincula otros objetos a este maestro. El maestro mantendrá el diccionario como una propiedad simple. Pienso en un árbol como el que se usa para la jerarquía de vistas en las aplicaciones de Cocoa.

Otra opción es crear un objeto de una clase dedicada que esté compuesta tanto por su diccionario de 'clase' como por un conjunto de todos los objetos relacionados con este diccionario. Esto es algo así como NSAutoreleasePoolen el cacao.

Mouviciel
fuente
7

A partir de Xcode 8, puede usar el atributo de propiedad de clase tal como lo respondió Berbie.

Sin embargo, en la implementación, debe definir tanto getter como setter de clase para la propiedad de clase utilizando una variable estática en lugar de un iVar.

Muestra.h

@interface Sample: NSObject
@property (class, retain) Sample *sharedSample;
@end

Muestra.m

@implementation Sample
static Sample *_sharedSample;
+ ( Sample *)sharedSample {
   if (_sharedSample==nil) {
      [Sample setSharedSample:_sharedSample];
   }
   return _sharedSample;
}

+ (void)setSharedSample:(Sample *)sample {
   _sharedSample = [[Sample alloc]init];
}
@end
Jean-Marie D.
fuente
2

Si tiene muchas propiedades de nivel de clase, entonces podría estar en orden un patrón singleton. Algo como esto:

// Foo.h
@interface Foo

+ (Foo *)singleton;

@property 1 ...
@property 2 ...
@property 3 ...

@end

Y

// Foo.m

#import "Foo.h"

@implementation Foo

static Foo *_singleton = nil;

+ (Foo *)singleton {
    if (_singleton == nil) _singleton = [[Foo alloc] init];

    return _singleton;
}

@synthesize property1;
@synthesize property2;
@synthesise property3;

@end

Ahora acceda a sus propiedades de nivel de clase de esta manera:

[Foo singleton].property1 = value;
value = [Foo singleton].property2;
Pedro Borges
fuente
44
Esta implementación de singleton no es segura para subprocesos, no la use
klefevre
1
Por supuesto, no es seguro para subprocesos, usted es la primera persona que menciona la seguridad de subprocesos y, por defecto, no tiene sentido la sobrecarga de seguridad de subprocesos en contextos no seguros para subprocesos, es decir, un solo subproceso.
Pedro Borges
Sería bastante fácil usar un dispatch_onceaquí.
Ian MacDonald
El PO quería una respuesta en el lado de la Declaración, no la implementación, e incluso la implementación sugerida es incompleta (no segura para subprocesos).
Motti Shneor
-3

[Pruebe esta solución, es simple] Puede crear una variable estática en una clase Swift y luego llamarla desde cualquier clase Objective-C.

jawad
fuente
1
OP no preguntó cómo crear una propiedad estática en Swift, esto no resuelve su problema.
Nathan F.
O más bien, resolvió el problema, pero no respondió la pregunta. No estoy seguro de si eso merece y vote hacia abajo ...
AmitaiB