Pregunta

Estoy usando el patrón singleton en varios lugares de una aplicación, y obtengo errores de pérdida de memoria de clang al analizar el código.

static MyClass *_sharedMyClass;
+ (MyClass *)sharedMyClass {
  @synchronized(self) {
    if (_sharedMyClass == nil)
      [[self alloc] init];
  }
  return _sharedMyClass;
}

// clang error: Object allocated on line 5 is no longer referenced after this point and has a retain count of +1 (object leaked)

Estoy usando esta configuración para scan-build :

scan-build -v -v -v -V -k xcodebuild

Estoy bastante seguro de que el código en el singleton está bien, después de todo, es el mismo código al que se hace referencia aquí en Stack Overflow, así como en la documentación de Apple, pero me gustaría solucionar la advertencia de pérdida de memoria. my scan-build devuelve el éxito.

¿Fue útil?

Solución

Puedo estar siendo excepcionalmente denso, pero seguramente tu línea 5

[[self alloc] init];

asigna un objeto del tipo de clase que lo contiene y lo tira rápidamente? ¿No quieres

_sharedMyClass = [[self alloc] init];

?

Otros consejos

Apple ha actualizado desde entonces su código único recomendado para pasar el analizador estático:

+ (MyGizmoClass*)sharedManager
{
    if (sharedGizmoManager == nil) {
        sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
    return sharedGizmoManager;
}

+ (id)allocWithZone:(NSZone *)zone
{
    return [[self sharedManager] retain];
}

Ahora + sharedManager llama a -allocWithZone: de super y asigna el retorno de -init , y el -allocWithZone de singleton: solo devuelve una SharedInstance retenida.

Editar:

¿Por qué la retención en + allocWithZone :?

+ allocWithZone: se anula porque alguien que usa MyGizmoClass podría sortear el singleton llamando a [[MyGizmoClass alloc] init] en lugar de [MyGizmoClass sharedManager]. Se retiene porque se espera que + alloc siempre devuelva un objeto con un recuento de retención de +1.

Cada llamada a + alloc debe equilibrarse con un -release o -autorelease, por lo que sin la retención en + allocWithZone :, la instancia compartida podría ser desasignada de otros usuarios.

Puede que le interese una implementación simple, de un método, basada en GCD (y, por lo tanto, solo 10.6+) publicada en Sitio de Mike Ash :

+ (id)sharedFoo
{
    static dispatch_once_t pred;
    static Foo *foo = nil;

    dispatch_once(&pred, ^{ foo = [[self alloc] init]; });
    return foo;
}

¡Estás haciendo referencia a self en un método de clase! Gran no-no! En segundo lugar, está llamando a [[self alloc] init] y simplemente está descartando la instancia. Debería asignar la referencia singleton en el método de clase, y no en init como supongo que está haciendo. A continuación, no existe una garantía real de que _sharedMyClass se inicialice a cero. Debe inicializarlo explícitamente en nil .

static MyClass *_sharedMyClass = nil;

+ (MyClass *)sharedMyClass {
  @synchronized(self) {
    if (_sharedMyClass == nil)
      _sharedMyClass = [[MyClass alloc] init];
  }
  return _sharedMyClass;
}

Probablemente también tenías esto allí también ...

+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedInstance == nil) {
            sharedInstance = [super allocWithZone:zone];
            return sharedInstance;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

La razón por la que no lo estaba almacenando en init es porque lo estaba almacenando en el método que llamó alloc. Este es el patrón que Apple tiene en sus ejemplos. Si también guarda el valor en su inicio, todo está bien y la advertencia desaparece. Dejaría sola la implementación de allocWithZone.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top