Проблема перевыпуска с объектами, захваченными блоком; сохранить количество прыжков прямо с +2 до 0! - PullRequest
8 голосов
/ 14 мая 2011

Меня смущает случайный сбой, который я наблюдаю, который, согласно инструменту Zombies, вызван чрезмерным выпуском некоторых значений словаря. Когда я смотрю на историю объектов для одного из этих переизданных объектов в Инструментах, я вижу, что количество сохраняемых объектов падает прямо с +2 до 0 на одном этапе. (Посмотрите на скриншоты в конце поста). Мне не понятно, как это вообще возможно.

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

В любом случае, я создаю CFDictionary, который содержит некоторые базовые базовые объекты (CFStrings и CFNumbers), и затем я приведу его к NSDictionary * и передам его в метод Objective-C. Упрощенная версия моего кода ниже:

// creates a CFDictionary containing some CFStrings and CFNumbers
void doStuff() 
{
    CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes();

    dispatch_async(myQueue, ^{
        [someObject receiveDictionary:(NSDictionary*)myDict];
        CFRelease(myDict);  // this line causes a crash. The Zombies instrument
                            // claims a CFString value contained in this
                            // dictionary has already been freed.
    });
}

// ...

- (void)receiveDictionary:(NSDictionary*)dict
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    NSString* str1 = [dict objectForKey:@"key1"];
    NSString* str2 = [dict objectForKey:@"key2"];
    NSNumber* num1 = [dict objectForKey:@"key3"];

    dispatch_async(myOtherQueue, ^{
        [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1];
    });

    [pool drain];
}

Я думал, что str1, str2 и num1 будут рассматриваться как объекты Objective-C, и поэтому будут захвачены и автоматически сохранены, когда блок в -receiveDictionary: будет скопирован с помощью вызова dispatch_async и освобождается при освобождении этого блока. Действительно, эти переменные, похоже, фиксируются и сохраняются блоком. Однако, изучая историю объекта для перевыпущенной CFString в Instruments, я вижу, что его счетчик ссылок увеличивается при копировании блока. В недоумении количество сохраняемых кадров падает с +2 до 0 при освобождении блока (см. Скриншот в конце поста); Я не знаю, как определить по трассировке стека, какой это блок. К тому времени, когда CFRelease вызывается в словаре в блоке в doStuff(), некоторые его значения уже были освобождены, и программа вылетает.

Так откуда взялся дополнительный релиз? Как может сохраниться счет объекта прямо с +2 до 0, как показывают инструменты?

По прихоти я заставил второй блок сохранить весь словарь, вот так:

dispatch_async(myOtherQueue, ^{
    [database executeUpdate:@"INSERT INTO blah (x,y,z) VALUES (?, ?, ?)", str1, str2, num1];
    [dict self];
});

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


Инструменты перечисляют следующую историю объекта для CFString зомби с указанием количества сохраненных объектов. Я включил скриншоты для интересных событий.

# 0 +1 CFString создано
# 1 +2 CFString добавлено в словарь
# 2 +1 CFString выпущен
# 3 +2 CFString сохраняется при копировании блока в -receiveDictionary:
# 4 +0 Что за ...? Число сохраняемых объектов уменьшилось с +2 до 0!
# 5 -1 CFDictionary выпущен, вызывая сбой

Ответы [ 3 ]

0 голосов
/ 14 июня 2011

Что вы используете в качестве CFDictionaryKeyCallBacks и CFDictionaryValueCallBacks при создании словаря в CreateMyDictionaryContainingCFTypes()? Я легко могу повторить эту проблему, если передам NULL для обоих, но я не смогу повторить ее, если передам &kCFTypeDictionaryKeyCallBacks и &kCFTypeDictionaryValueCallBacks.

0 голосов
/ 18 июня 2011

Наконец-то поймал ошибку - оказалось, что это была не проблема зомби, а не связанная с этим проблема повреждения памяти в процедуре декодирования данных base64.Ничего общего с сохранением / выпуском, блоками или GCD.Вздох.

Оглядываясь назад, это должно было быть более очевидным.Тот факт, что моя программа аварийно завершилась вскоре после того, как Инструменты сообщили, что переизданный объект должен был быть подсказкой - если бы это была на самом деле проблема зомби, вы бы не ожидали сбоя.(Я так думаю?) Подсчет количества прыжков с +2 до 0, вероятно, предполагает и нечто иное, чем простое перевыпуск.

Так что же я узнал?

  • Не копироватьвставьте код без тщательной проверки.Все процедуры преобразования base64 не созданы равными.(В частности, вызов realloc без использования его возвращаемого значения является неправильным, неправильным неправильным! Жаль, что Статический анализатор не помечает это.)
  • Не полагайтесь исключительно на инструменты - другие инструменты, такие как Valgrindможет быть полезнымВ этом случае Вальгринд дал мне более точную информацию.
0 голосов
/ 14 мая 2011

Блок при копировании неявно сохранит любой объект Objective-C в своей области, а затем также неявно освободит эти объекты при освобождении блока.

CFDictionaryRef - это бесплатный тип моста для NSDictionary, и, что касается блоков, также объекты Objective-C.Это означает, что вам не нужно выполнять дополнительное управление памятью.

Позвольте мне прокомментировать ваш код и отметить порядок оценки.

void doStuff() {
    // 1. myDict must have a retainCount of 1, you named your function Create
    //    and promised so according to Core Foundation men.rules.
    CFDictionaryRef myDict = CreateMyDictionaryContainingCFTypes();

    // 2. dispatch_async will copy your block and retain myDict, since it is in 
    //    scope of the block, myDict has a retainCount of 2
    dispatch_async(myQueue, ^{
        // 4. Block is execute some time later, myDict has a retainCount of 1.
        [someObject receiveDictionary:(NSDictionary*)myDict];

        // 5. Block is done and will be released, along with scoped objects
        //    on exit, retainCount reaches 0, and myDict is released.
    });

    // 3. Release your own copy before function ends, retainCount of 1
    CFRelease(myDict);  // this line causes a crash. The Zombies instrument
}
...