Предупреждения об одиночке Objective-C и LLVM / clang - PullRequest
2 голосов
/ 06 мая 2009

Я использую шаблон синглтона в нескольких местах приложения и получаю ошибки утечки памяти из 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

Я вполне уверен, что код в синглтоне просто в порядке - в конце концов, это тот же код, на который ссылаются здесь при переполнении стека, а также в документации Apple, - но я бы хотел отсортировать предупреждение об утечке памяти так, моя скан-сборка возвращает успех.

Ответы [ 5 ]

6 голосов
/ 06 мая 2009

Я могу быть исключительно плотным, но, конечно, ваша линия 5

[[self alloc] init];

выделяет объект типа, содержащего класс, и быстро выбрасывает его? Ты не хочешь

_sharedMyClass = [[self alloc] init];

5 голосов
/ 08 декабря 2009

Apple с тех пор обновила рекомендуемый одноэлементный код для прохождения статического анализатора:

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

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

Теперь +sharedManager вызывает super * -allocWithZone: и присваивает возврат -init, а одиночный -allocWithZone: просто возвращает сохраненный sharedInstance.

Редактировать:

Зачем сохранять в + allocWithZone:?

+ allocWithZone: переопределяется, потому что кто-то, использующий MyGizmoClass, может обойти синглтон, вызвав [[MyGizmoClass alloc] init] вместо [MyGizmoClass sharedManager]. Он сохраняется, поскольку ожидается, что + alloc всегда будет возвращать объект со счетом сохранения + 1.

Каждый вызов + alloc должен быть сбалансирован с -release или -autorelease, поэтому без сохранения в + allocWithZone: общий ресурс может потенциально быть освобожден из-под других пользователей.

4 голосов
/ 13 ноября 2009

Возможно, вас заинтересует простая одноэлементная одноэлементная реализация на основе GCD (и, следовательно, только 10.6+), размещенная на сайте Майка Эша :

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

    dispatch_once(&pred, ^{ foo = [[self alloc] init]; });
    return foo;
}
1 голос
/ 18 августа 2012

Вы ссылаетесь на self в методе класса! Большой нет-нет! Во-вторых, вы звоните [[self alloc] init] и просто отбрасываете экземпляр. Вы должны назначить одноэлементную ссылку в методе класса, а не в init, как я предполагаю, что вы делаете. Далее, нет никакой реальной гарантии, что _sharedMyClass будет инициализирован нулем. Вы должны явно инициализировать его как nil.

static MyClass *_sharedMyClass = nil;

+ (MyClass *)sharedMyClass {
  @synchronized(self) {
    if (_sharedMyClass == nil)
      _sharedMyClass = [[MyClass alloc] init];
  }
  return _sharedMyClass;
}
0 голосов
/ 06 ноября 2009

У тебя тоже, наверное, было это там ...

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

...