У меня есть doozey проблемы, над которой я работал в течение нескольких недель. Это связано с заиканием производительности пользовательского интерфейса, когда я сохраняю контекст управляемого объекта данных. Я добрался, насколько могу, сам и ищу какую-то помощь.
Ситуация
В моем приложении используются два экземпляра NSManagedObjectContext
. Один принадлежит делегату приложения и имеет прикрепленный к нему постоянный координатор хранилища. Другой является дочерним элементом основного MOC и относится к объекту Class
, называемому PhotoFetcher
. Он использует NSPrivateQueueConcurrencyType
, поэтому все операции, выполняемые в этом MOC, выполняются в фоновом режиме.
Наше приложение загружает данные JSON, представляющие данные о фотографиях из нашего API. Чтобы получить данные из нашего API, выполняется следующая последовательность шагов:
- Создайте объект
NSURLRequest
и используйте протоколNSURLConnectionDataDelegate
для построения данных, возвращенных из запроса, или обрабатывайте ошибки. - После завершения загрузки данных JSON выполните блок вторичной очереди MOC, который выполняет следующие действия:
- Разберите JSON с помощью
NSJSONSerialization
в экземплярах класса Foundation. - Итерации по анализируемым данным, вставка или обновление объектов в моем фоновом контексте по мере необходимости. Как правило, это приводит к появлению около 300 новых или обновленных объектов.
- Сохранить фоновый контекст. Это распространяет мои изменения на основной MOC.
- Выполните блок на основном MOC, чтобы сохранить его контекст. Это значит, что наши данные сохраняются на диске, a
SQLite
. Наконец, сделайте обратный вызов делегату, сообщив им, что ответ полностью вставлен в хранилище основных данных.
- Разберите JSON с помощью
Код для сохранения фона MOC выглядит примерно так:
[AppDelegate.managedObjectContext performBlock:^{
[AppDelegate saveContext]; //A standard save: call to the main MOC
}];
Когда сохраняется контекст основного объекта, он также сохраняет большое количество JPEG файлов, которые были загружены с момента последнего сохранения сохранения контекста основного объекта. В настоящее время на iPhone 4 мы загружаем 15 200x200 JPEG с 70% сжатием или всего около 2 МБ данных.
Проблема
Это работает и работает хорошо. Моя проблема заключается в том, что после сохранения фонового контекста NSFetchedResultsController
, запущенный в моем контроллере просмотра, получает изменения, распространяемые до основного MOC. Он вставляет новые ячейки в наш PSTCollectionView
, клон с открытым исходным кодом UICollectionView
. При вставке новых ячеек основной контекст сохраняет и записывает эти изменения на диск. Это может произойти на iPhone 4 под управлением iOS 5.1 в любом месте от 250-350 мс.
В течение третьей секунды приложение полностью не отвечает. Анимация, выполнявшаяся до сохранения, приостанавливается, и никакие новые пользовательские события не отправляются в цикл основного запуска до тех пор, пока сохранение не завершится.
Я запустил наше приложение в Инструментах с помощью Time Profiler, чтобы определить, что блокирует наш основной поток. К сожалению, результаты были довольно непрозрачными. Это самая тяжелая трассировка стека из инструментов.
Кажется, он сохранил обновления в постоянном хранилище, но я не мог быть уверен. Поэтому я вообще удалил любые вызовы на saveContext
, чтобы MOC не касался диска, и блокирующий вызов в основном потоке все еще сохраняется.
Трассировка в текстовой форме выглядит следующим образом:
Symbol Name
-[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:]
-[NSManagedObjectContext executeFetchRequest:error:]
-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]
_perform
_dispatch_barrier_sync_f_invoke
_dispatch_client_callout
__82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke_0
-[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:]
-[NSManagedObjectContext executeFetchRequest:error:]
-[NSPersistentStoreCoordinator executeRequest:withContext:error:]
-[NSSQLCore executeRequest:withContext:error:]
-[NSSQLCore objectsForFetchRequest:inContext:]
-[NSSQLCore newRowsForFetchPlan:]
-[NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument:]
-[NSSQLiteConnection execute]
Что я пробовал
Прежде чем мы коснулись кода Core Data, первое, что мы сделали, это оптимизировать наши JPEG. Мы переключились на меньшие JPEG и увидели повышение производительности. Затем мы уменьшили количество загружаемых файлов JPEG (от 90 до 15). Это также приводит к значительному повышению производительности. Тем не менее, мы по-прежнему видим блоки длиной 250-350 мс в основном потоке.
Первое, что я пробовал, - это просто избавиться от фонового MOC, чтобы исключить вероятность того, что это может вызвать проблемы. Фактически, это ухудшало ситуацию, так как наш код обновления или создания был запущен в основном потоке и приводил к ухудшению общей производительности анимации.
Изменение постоянного хранилища до NSInMemoryStoreType
не имело эффекта.
Может ли кто-нибудь указать мне на "секретный соус", который даст мне представление пользовательского интерфейса, которое обещали фоновые контексты объектов?