8

Este es un pequeño detalle, pero cada vez que cargo algo perezoso me quedo atrapado en él. ¿Son ambos métodos aceptables? ¿Es mejor? Suponga que la variable tiene la propiedad de retención.Carga lenta en Objective-C: ¿Debería llamar al setter desde el getter?

Método # 1

(AnObject *)theObject{ 
    if (theObject == nil){ 
     theObject = [[AnObject createAnAutoreleasedObject] retain]; 
    } 
    return theObject; 
} 

Método # 2

(AnObject *)theObject{ 
    if (theObject == nil){ 
     self.theObject = [AnObject createAnAutoreleasedObject]; 
    } 
    return theObject; 
} 

En primer lugar, no estoy seguro de si está bien para acceder a otra función de acceso dentro de un descriptor de acceso (no veo por qué no, aunque). Pero parece que establecer la variable de clase sin atravesar el setter podría ser igualmente malo si el setter hace algo especial (o si la propiedad se cambia a algo además de retener y el getter no está marcado).

Respuesta

17

Ambos son realmente bastante frágiles y para nada idénticos, dependiendo de lo que estén haciendo los clientes de la clase. Hacerlos idénticos es bastante fácil, ver más abajo, pero hacerlo menos frágil es más difícil. Tal es el precio de la inicialización lenta (y por qué generalmente trato de evitar la inicialización lenta de esta manera, prefiriendo tratar la inicialización de los subsistemas como parte de la administración general del estado de la aplicación).

Con el n. ° 1, está evitando el armador y, por lo tanto, cualquier cosa que observe el cambio no verá el cambio. Al "observar", me refiero específicamente a la observación de valores clave (incluidos los enlaces de cacao, que utiliza KVO para actualizar la IU automáticamente).

Con el n. ° 2, activará la notificación de cambio, actualizando la UI y de otra manera exactamente como si se llamara al sistema.

En ambos casos, tiene un potencial de recursión infinita si la inicialización del objeto llama al captador. Eso incluye si un observador solicita el valor anterior como parte de la notificación de cambio. No hagas eso.

Si va a utilizar cualquiera de los métodos, considere cuidadosamente las consecuencias. Uno tiene el potencial de dejar la aplicación en un estado incoherente porque un cambio de estado de una propiedad no notificó y el otro tiene el potencial de un punto muerto.

Es mejor evitar el problema por completo. Vea abajo.


tener en cuenta (recolección de basura en la herramienta de línea de comandos de cacao estándar:.

#import <Foundation/Foundation.h> 

@interface Foo : NSObject 
{ 
    NSString *bar; 
} 
@property(nonatomic, retain) NSString *bar; 
@end 
@implementation Foo 
- (NSString *) bar 
{ 
    if (!bar) { 
     NSLog(@"[%@ %@] lazy setting", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); 
     [self willChangeValueForKey: @"bar"]; 
     bar = @"lazy value"; 
     [self didChangeValueForKey: @"bar"]; 
    } 
    return bar; 
} 

- (void) setBar: (NSString *) aString 
{ 
    NSLog(@"[%@ %@] setting value %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aString); 
    bar = aString; 
} 
@end 

@interface Bar:NSObject 
@end 
@implementation Bar 
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; 
{ 
    NSLog(@"[%@ %@] %@ changed\n\tchange:%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), keyPath, change); 
} 
@end 

int main (int argc, const char * argv[]) { 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    Foo *foo = [Foo new]; 
    Bar *observer = [Bar new]; 
    CFRetain(observer); 
    [foo addObserver:observer forKeyPath:@"bar" 
      options: NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionNew 
      context:NULL]; 
    foo.bar; 
    foo.bar = @"baz"; 
    CFRelease(observer); 

    [pool drain]; 
    return 0; 
} 

Esto no cuelgue Es expulsado:

2010-09-15 12:29:18.377 foobar[27795:903] [Foo bar] lazy setting 
2010-09-15 12:29:18.396 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed 
    change:{ 
    kind = 1; 
    notificationIsPrior = 1; 
} 
2010-09-15 12:29:18.397 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed 
    change:{ 
    kind = 1; 
    new = "lazy value"; 
} 
2010-09-15 12:29:18.400 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed 
    change:{ 
    kind = 1; 
    notificationIsPrior = 1; 
} 
2010-09-15 12:29:18.400 foobar[27795:903] [Foo setBar:] setting value baz 
2010-09-15 12:29:18.401 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed 
    change:{ 
    kind = 1; 
    new = baz; 
} 

Si se va a añadir NSKeyValueObservingOptionOld a la lista de opciones de observación, se cuelga mucho.

Volviendo a un comentario que hice anteriormente; la mejor solución es no hacer la inicialización lenta como parte de su getter/setter. Es muy fino. Es mucho mejor que administres tu estado de gráfico de objetos en un nivel superior y, como parte de eso, tengas una transición de estado que es básicamente del "Yo! Voy a usar este subsistema ahora. ¡Calienta a ese chico malo! " eso hace la inicialización perezosa.

+0

Esta es la respuesta correcta en mi opinión. –

+1

En realidad, no hay ningún cambio que observar al inicializar el objeto de forma perezosa, por lo que esta respuesta es ** no ** correcta. – Sven

+0

¿Eh? Ya sea que se inicializa de forma perezosa o no es irrelevante. Si configuro una observación de KVO de 'theObject' y pongo' self.theObject = ... 'en el getter, entonces esa observación de KV ** disparará **. "Inicialización lenta" es solo un nombre para un patrón; ni el compilador ni el tiempo de ejecución saben nada al respecto. – bbum

0

Ambas son básicamente idénticas, depende de usted elegir cuál es la mejor para su caso. Ya describió realmente los pros/contras sobre el uso de la sintaxis de la propiedad.

+0

No, no lo son. # 2 está mal! – Sven

1

Si sabe que el método de establecimiento de propiedades es un setter de retención estándar, son idénticas. De lo contrario, debe decidir si se debe invocar el otro comportamiento del colocador durante esa operación. Si no lo sabe, es más seguro usar el colocador, ya que su comportamiento puede ser importante. No te preocupes

+0

Podría ser lo mismo desde el punto de vista de administración de memoria. Pero los setters hacen más que solo encargarse de la administración de la memoria, también hay KVO en qué pensar. ¡Y eso hace que el método # 2 esté equivocado! – Sven

3

Esos métodos nunca son idénticos. El primero es correcto, mientras que el segundo es mal! Un getter nunca puede llamar al will/didChangeValueForKey: y por lo tanto tampoco al setter. Esto conducirá a la recursión infinita si se observa esa propiedad.

Y además, no hay cambios de estado para observar cuando se inicializa el miembro. Le preguntas a tu objeto por el theObject y lo obtienes. Cuando esto se crea es un detalle de implementación y no se refiere al mundo exterior.

+0

Si hay observadores en existencia antes de la llamada al comprador, hacer que el comprador modifique el estado sin activar las notificaciones de observación dejará la aplicación en un estado incoherente. – bbum

+0

En otras palabras, ambos métodos son incorrectos ... – bbum

+1

No, la configuración del enlace siempre llamará al getter. No hay forma de que el observador entre en un estado inconsistente. – Sven