Singletons Objective-C e avvisi di perdite LLVM / clang
-
06-07-2019 - |
Domanda
Sto usando il modello singleton in diversi punti di un'applicazione e ricevo errori di perdita di memoria da clang
durante l'analisi del codice.
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)
Sto usando queste impostazioni per scan-build
:
scan-build -v -v -v -V -k xcodebuild
Sono abbastanza certo che il codice nel singleton va bene - dopotutto, è lo stesso codice a cui fa riferimento qui su Stack Overflow e nella documentazione di Apple - ma vorrei che l'avviso di perdita di memoria venisse risolto così il mio scan-build restituisce successo.
Soluzione
Potrei essere eccezionalmente denso, ma sicuramente la tua linea 5
[[self alloc] init];
alloca un oggetto del tipo di classe contenente e lo butta via immediatamente? Non vuoi
_sharedMyClass = [[self alloc] init];
Altri suggerimenti
Da allora Apple ha aggiornato codice singleton consigliato per passare l'analizzatore statico:
+ (MyGizmoClass*)sharedManager
{
if (sharedGizmoManager == nil) {
sharedGizmoManager = [[super allocWithZone:NULL] init];
}
return sharedGizmoManager;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedManager] retain];
}
Ora + sharedManager
chiama il -allocWithZone di super:
e assegna il ritorno di -init
e il -allocWithZone del singleton:
restituisce solo un'istanza condivisa mantenuta.
Modifica:
Perché conservare in + allocWithZone :?
+ allocWithZone: viene sostituito perché qualcuno che utilizza MyGizmoClass potrebbe aggirare il singleton chiamando [[MyGizmoClass alloc] init] invece di [MyGizmoClass sharedManager]. Viene mantenuto perché + alloc dovrebbe restituire sempre un oggetto con un conteggio di mantenimento di +1.
Ogni chiamata a + alloc dovrebbe essere bilanciata con un -release o -autorelease, quindi senza la conservazione in + allocWithZone :, l'istanza condivisa potrebbe potenzialmente essere distribuita da altri utenti.
Potresti essere interessato a una semplice implementazione single-method basata su GCD (e quindi solo 10.6+) pubblicata su Sito di Mike Ash :
+ (id)sharedFoo
{
static dispatch_once_t pred;
static Foo *foo = nil;
dispatch_once(&pred, ^{ foo = [[self alloc] init]; });
return foo;
}
Stai facendo riferimento a self
in un metodo di classe! Grande no-no! In secondo luogo, stai chiamando [[self alloc] init]
e stai semplicemente gettando via l'istanza. Dovresti assegnare il riferimento singleton nel metodo class e non in init
come immagino tu stia facendo. Successivamente, non vi è alcuna reale garanzia che _sharedMyClass
verrà inizializzato su zero. Dovresti inizializzarlo esplicitamente su nil
.
static MyClass *_sharedMyClass = nil;
+ (MyClass *)sharedMyClass {
@synchronized(self) {
if (_sharedMyClass == nil)
_sharedMyClass = [[MyClass alloc] init];
}
return _sharedMyClass;
}
Probabilmente avevi anche questo dentro ...
+ (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
}
Il motivo per cui non lo stavi memorizzando in init è perché lo stavi memorizzando nel metodo chiamato alloc. Questo è lo schema che Apple ha nei loro esempi. Se salvate il valore anche nel vostro init, tutto va bene e l'avvertimento scompare. Lascerei da sola l'implementazione allocWithZone.