По какой-то причине совместное использование dispatch_async и Core Data приводит к тому, что мое приложение полностью зависает, но не падает.
Симптом
Пользовательский интерфейс не отвечает. Приложение не вылетает.
Приложение использует [[UIAccelermeter sharedAccelerometer] setDelegate: self]
для непрерывного получения значений акселерометра. Обратный вызов "accelerometer:didAccelerate:
" выполняется в основном потоке. При возникновении этой проблемы обратный вызов больше не выполняется.
Поэтому основной поток оказывается "замороженным".
Контекст
Я использую NSURLConnection
в своем приложении для отправки HTTP-запросов на мой сервер. В connectionDidFinishLoading, где обрабатываются данные ответа, выполнение занимает около 2 секунд, и пользовательский интерфейс не отвечает в течение этих 2 секунд, поскольку он выполняется в основном потоке.
После некоторых исследований кажется, что gcd/dispatch_async
является лучшим решением для этой ситуации, чтобы выполнить обработку данных в фоновом режиме. Итак, в connectionDidFinishLoading
я добавил следующие вызовы gcd:
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self handleTheData: connection];
});
Иногда это работает нормально, но это может привести к случайному зависанию приложения. Несмотря на то, что используется один и тот же код с одними и теми же данными в хранилище Core Data, повторные тесты показывают, что симптом можно наблюдать в X% случаев. Вероятность возникновения проблемы зависит от нескольких факторов, описанных ниже.
Что делает handleTheData()?
Он проверяет данные ответа от NSURLConnection
, которые представляют собой строку в формате JSON. Строка представляет собой 3 списка сущностей, и каждая сущность представляет собой массив массивов чисел. Итак, есть коды для преобразования строки в формате JSON в 3 NSMutableArray настраиваемого объекта, где каждый объект является подклассом NSManagedObject.
Во время преобразования, поскольку эти объекты являются «временными» в том смысле, что они могут храниться или не храниться постоянно, они создаются в «нулевом» NSManagedObjectContext, например:
SampleEntity *newEntity=
(SampleEntity *) [[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:nil
];
Таким образом, они не будут смешиваться с основным контекстом.
Затем эти новые объекты сравниваются с существующими объектами в хранилище данных. Таким образом, есть некоторые коды, которые нужно многократно извлекать из хранилища данных, чтобы увидеть, существует ли уже конкретный новый объект, и в зависимости от данных нового объекта новый объект может быть сохранен в хранилище данных или существующий объект может быть обновлен.
[managedObjectContext save: &error] часто вызывается, чтобы обеспечить постоянное сохранение изменений.
Что-то еще работает в фоновом режиме?
Как упоминалось в разделе «Симптомы», акселерометр работает в фоновом режиме в основном потоке с использованием [[UIAccelermeter sharedAccelerometer] setDelegate: self]
AudioUnits также настроены для работы в фоновом режиме (в отдельном «приоритетном» потоке согласно документам Apple) для воспроизведения звука.
Выводы по причине проблемы
Было установлено, что на вероятность возникновения проблемы влияют следующие факторы:
- Если какой-либо отладочный код закомментирован, например NSLog(@"fetchedResult: %@", fetchedResult), вероятность возникновения проблемы уменьшается (но она все еще возникает).
- Если акселерометр не используется, проблема почти никогда не возникает (но не на 100%, так как я провел ограниченное количество тестов).
- Если используется акселерометр, вероятность возникновения проблемы составляет примерно 50/50.
- Если некоторые из кодов основных данных, такие как [sampleEntity setXYZ..], закомментированы, проблема почти никогда не возникает, даже если используется акселерометр.
Второй выпуск
Кроме того, даже если основной поток не заморожен, возникает ДРУГАЯ ПРОБЛЕМА. Код обработки данных завершится на полпути без ошибок.
- Это означает, что обработка данных будет выполнена лишь частично, и ошибки не возникнут, а основной поток продолжит нормально работать.
- Эта вторая проблема возникает часто (но не всегда) всякий раз, когда основная проблема не возникает.
Интересная проблема, не так ли? :)
Изменить
- 01.03.2013 - Ранее я думал, что причиной проблемы была iOS 6. Извините, это было совершенно неправильно. Изменено описание, чтобы отразить новые результаты.