Предупреждения об утечках Objective-C Singletons и LLVM/clang

StackOverflow https://stackoverflow.com/questions/830376

  •  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 в покое.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top