Avertissements de fuite Objective-C Singletons et LLVM / Clang
-
06-07-2019 - |
Question
J'utilise le motif singleton à plusieurs endroits d'une application et j'obtiens des erreurs de fuite de mémoire de clang
lors de l'analyse du code.
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)
J'utilise ces paramètres pour scan-build
:
scan-build -v -v -v -V -k xcodebuild
Je suis à peu près sûr que le code dans le singleton convient parfaitement. Après tout, il s'agit du même code référencé ici dans Stack Overflow ainsi que dans la documentation d'Apple, mais j'aimerais que l'avertissement concernant les fuites de mémoire soit résolu. mon scan-build renvoie le succès.
La solution
Je suis peut-être exceptionnellement dense, mais votre ligne 5 est sûrement
[[self alloc] init];
alloue un objet du type classe contenant et le jette rapidement? Ne veux-tu pas
_sharedMyClass = [[self alloc] init];
?
Autres conseils
Apple a depuis mis à jour son code de singleton recommandé pour passer l'analyseur statique:
+ (MyGizmoClass*)sharedManager
{
if (sharedGizmoManager == nil) {
sharedGizmoManager = [[super allocWithZone:NULL] init];
}
return sharedGizmoManager;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedManager] retain];
}
Now + sharedManager
appelle le -allocWithZone:
du super et attribue le retour de -init
, et le -allocWithZone du singleton:
renvoie simplement une instance partagée non conservée.
Modifier:
Pourquoi conserver dans + allocWithZone:?
+ allocWithZone: est remplacé car une personne utilisant MyGizmoClass pourrait contourner le singleton en appelant [[MyGizmoClass alloc] init] au lieu de [MyGizmoClass sharedManager]. Il est conservé, car + alloc devrait toujours renvoyer un objet avec un nombre de retenues égal à +1.
Chaque appel à + alloc doit être équilibré avec -release ou -autorelease. Par conséquent, sans la retenue dans + allocWithZone :, l'instance partagée pourrait éventuellement être désallouée sous d'autres utilisateurs.
Vous pouvez être intéressé par une implémentation de singleton simple, à une méthode, basée sur GCD (et donc 10.6+ uniquement) postée le Le site de Mike Ash :
+ (id)sharedFoo
{
static dispatch_once_t pred;
static Foo *foo = nil;
dispatch_once(&pred, ^{ foo = [[self alloc] init]; });
return foo;
}
Vous faites référence à self
dans une méthode de classe! Big non-non! Deuxièmement, vous appelez [[self alloc] init]
et vous vous contentez de jeter l'instance. Vous devez assigner la référence singleton dans la méthode class et non dans init
comme je le suppose. Ensuite, rien ne garantit que _sharedMyClass
sera initialisé à zéro. Vous devez explicitement l’initialiser sur nil
.
static MyClass *_sharedMyClass = nil;
+ (MyClass *)sharedMyClass {
@synchronized(self) {
if (_sharedMyClass == nil)
_sharedMyClass = [[MyClass alloc] init];
}
return _sharedMyClass;
}
Vous en avez probablement aussi eu ici aussi
+ (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
}
Si vous ne le stockiez pas dans init, c'est parce que vous le stockiez dans la méthode que alloc a appelée. C'est le modèle qu'Apple utilise dans ses exemples. Si vous enregistrez également la valeur dans votre init, tout va bien et l'avertissement disparaît. Je laisserais seul l'implémentation allocWithZone.