Предупреждения об утечках Objective-C Singletons и LLVM/clang
-
06-07-2019 - |
Вопрос
Я использую шаблон Singleton в нескольких местах приложения и получаю ошибки утечки памяти из-за clang
при анализе кода.
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)
Я использую эти настройки для scan-build
:
scan-build -v -v -v -V -k xcodebuild
Я совершенно уверен, что код в синглтоне в порядке — в конце концов, это тот же код, который упоминается здесь, в Stack Overflow, а также в документации Apple — но я хотел бы разобраться с предупреждением об утечке памяти, чтобы мое сканирование… build возвращает успех.
Решение
Возможно, я слишком туп, но ваша фраза 5 наверняка
[[self alloc] init];
выделяет объект типа содержащего его класса и сразу же выбрасывает его?ты не хочешь
_sharedMyClass = [[self alloc] init];
?
Другие советы
Apple с тех пор обновила свои рекомендовал одноэлементный код для прохождения статического анализатора:
+ (MyGizmoClass*)sharedManager
{
if (sharedGizmoManager == nil) {
sharedGizmoManager = [[super allocWithZone:NULL] init];
}
return sharedGizmoManager;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedManager] retain];
}
Теперь + sharedManager
вызывает супер -allocWithZone:
и присваивает возврат -init
и -allocWithZone синглтона:
просто возвращает сохраненный sharedInstance.
Изменить.
Зачем сохранять в + allocWithZone:?
+ allocWithZone: переопределяется, потому что кто-то, использующий MyGizmoClass, может обойти одиночный вызов, вызвав [[MyGizmoClass alloc] init] вместо [MyGizmoClass sharedManager]. Он сохраняется, поскольку ожидается, что + alloc всегда будет возвращать объект со счетом сохранения +1.
Каждый вызов + alloc должен быть сбалансирован с помощью -release или -autorelease, поэтому без сохранения в + allocWithZone: общий ресурс потенциально может быть освобожден из-под других пользователей.
Возможно, вас заинтересует простая одноэлементная одноэлементная реализация на основе GCD (и, следовательно, только 10.6+), размещенная на сайт Майка Эша :
+ (id)sharedFoo
{
static dispatch_once_t pred;
static Foo *foo = nil;
dispatch_once(&pred, ^{ foo = [[self alloc] init]; });
return foo;
}
Вы ссылаетесь на self
в методе класса! Большой нет-нет! Во-вторых, вы вызываете [[self alloc] init]
и просто отбрасываете экземпляр. Вы должны назначить одноэлементную ссылку в методе класса, а не в init
, как я предполагаю, что вы делаете. Далее, нет никакой реальной гарантии, что _sharedMyClass
будет инициализирован нулем. Вы должны явно инициализировать его как nil
.
static MyClass *_sharedMyClass = nil;
+ (MyClass *)sharedMyClass {
@synchronized(self) {
if (_sharedMyClass == nil)
_sharedMyClass = [[MyClass alloc] init];
}
return _sharedMyClass;
}
Возможно, у вас там тоже было это ...
+ (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
}
Причина, по которой вы не сохранили его в init, заключается в том, что вы хранили его в методе, который вызвал alloc. Это образец Apple в своих примерах. Если вы также сохраните значение в своем init, все в порядке, и предупреждение исчезнет. Я бы оставил реализацию allocWithZone в покое.