IOS5 сбой во время runMode: beforeDate:

У меня проблема с совместимостью моего приложения с версиями iOS5 b7 и GM.

Проблема возникает в следующих строках кода:

do {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);

Сбой приложения с сигналом EXC_BAD_ACCESS после некоторых итераций.

Число прошедших итераций является случайным (от 2 до 7).

Также все хорошо работает на iOS4 и iOS3.

Такая же проблема возникает в примере Apple: XMLPerformance Sample.

Что вы думаете об этом?

12 октября тысячи пользователей моего приложения будут обновлены до iOS5, и я не хочу, чтобы мое приложение было с такой странной ошибкой в ​​AppStore.

Ответ 1

Прошло 4 часа, и я нашел проблему. Я опишу, как я решил проблему в XMLPerformance sample.

Проблема была в NSAutoreleasePool. Существует @property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;. Когда приложение начинает загружать Top300 Paid Apps RSS, новый поток создается с помощью [NSThread detachNewThreadSelector:@selector(downloadAndParse:) toTarget:self withObject:url];. Поэтому в этой теме мы должны поддерживать локальный пул авторесурсов. Это делается следующим образом:

- (void)downloadAndParse:(NSURL *)url {
    self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];

    // initializing internet connection and libxml parser.
    if (rssConnection != nil) {
         do {
             [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
         } while (!done);
    }
    // Release resources used only in this thread.
    [downloadAndParsePool release]; 
    self.downloadAndParsePool = nil;
}

Итак, в downloadAndParse: все выглядит отлично. Теперь рассмотрим один метод, который вызывается при анализе элемента из RSS:

- (void)finishedCurrentSong {
    // sending new item to delegate and other ...
    countOfParsedSongs++;
    // Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the 
    // size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but 
    // taking this action too frequently would be wasteful and reduce performance.
    if (countOfParsedSongs == kAutoreleasePoolPurgeFrequency) {
        [downloadAndParsePool release];
        self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
        countOfParsedSongs = 0;
    }
}

Как вы видите там строки:

[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];

Так точно, что строки вызывают исключение. Если я прокомментирую их, все будет отлично.

Но я решил не только прокомментировать эти строки, но и заменить NSAutoreleasePool на - (void)downloadAndParse:(NSURL *)url блоком @autorelease, поскольку сказано, что он более эффективен:

- (void)downloadAndParse:(NSURL *)url {
@autoreleasepool {

    // initializing internet connection and libxml parser.
    if (rssConnection != nil) {
         do {
             [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
         } while (!done);
    }
    // Release resources used only in this thread.
    }
}

Теперь все работает нормально. Единственная проблема, которую я не разрешил:

// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the 
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but 
// taking this action too frequently would be wasteful and reduce performance.

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

Спасибо.

Ответ 2

Это выглядит как проблема с памятью, пожалуйста, проверьте Apple Technote QA1367 " Поиск ошибок EXC_BAD_ACCESS в проекте Cocoa"

В своем коде попробуйте как можно скорее сбой:

[item release], item = nil;

Он не решает проблему, просто делает крах случаться раньше и, надеюсь, даст вам более значимый столбец для изучения.

Если вы используете многопоточность, хорошо... Вы можете попытаться напечатать "текущий" идентификатор потока в консоли, чтобы убедиться, что все действительно запущено в потоке, где вы ожидаете, что они будут Бег. Особенно убедитесь, что все материалы пользовательского интерфейса находятся в основном потоке, даже если такой код запускается как побочный эффект другого кода (возможно, всплывающие окна с ошибкой).

#include <pthread.h>
- (void)myFunction
{
    NSLog(@"Thread (%d)",
        pthread_mach_thread_np(pthread_self()));
}

Запустите приложение с помощью Инструменты, убедитесь, что проверка памяти происходит каждые 1 или 2 секунды. Медленно, но вы снова хотите получить уведомление как можно ближе к актуальной проблеме памяти.

Ответ 3

Глядя на ваш код: откуда появилась эта "сделанная" переменная, и кто меняет ее значение и когда? Теперь это выглядит довольно волшебным.

Также вы можете проверить возвращаемое значение runMode: beforeDate, чтобы убедиться, что он запущен. Если возвращаемое значение равно NO, runLoop не запускался вообще. Может быть, какая-то другая часть вашего кода не может справиться с таким случаем?

Ответ 4

Просто мой маленький вклад.

Поскольку у меня такая же проблема, я обнаружил, что в iOS5 вам не нужно иметь собственный NSAutoreleasePool в потоке (используемом performSelectorOnMainThread).

Тогда, в вашем коде (синтаксический анализатор xml - тот же, что и я), я думаю, вам нужно отделить код от iOS4 и iOS5.

С iOS4 вам нужен NSAutoreleasePool, но не с iOS5.