Я только что «решил» то, что кажется проблемой тупика или синхронизации:
[NSThread sleepForTimeInterval:0.1];
в приложении, которое прикрепляет ссылки на свойства MPMediaItem (музыка/изображения) из библиотеки IPOD к экземплярам объектов, и эти объекты сохраняются с помощью CoreData. Мой интерес здесь состоит в том, чтобы точно понять, что происходит и как лучше всего действовать в этой ситуации. Вот оно:
Рецепт повторения этого каждый раз следующий:
Пользователь создает новый проект.
doc = [[UIManagedDocument alloc] initWithFileURL:docURL]; if (![[NSFileManager defaultManager] fileExistsAtPath:[docURL path]]) { [doc saveToURL:docURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) { if (success) { completionBlock(doc); } else { DLog(@"Failed document creation: %@", doc.localizedName); } }];
Позже управляемыйObjectContext используется для связывания экземпляров объекта и гидратации модели CoreData.
TheProject *theProject = [TheProject projectWithInfo:theProjectInfo inManagedObjectContext:doc.managedObjectContext];
Позже пользователь создает объект «CustomAction», добавляет к нему «ChElement» и связывает «MusicElement» с ChElement. (Это псевдонимы для объектов модели CoreData). MusicElement добавляется через библиотеку IPOD.
#define PLAYER [MPMusicPlayerController iPodMusicPlayer]
Пользователь сохраняет этот проект, а затем переключается на существующий проект, в котором уже создан один объект CustomAction с элементами ChElement и MusicElement.
Пользователь выбирает этот ChElement из tableView и переходит к detailView. При переходе от ChElementTVC (подкласс класса CoreData TableViewController, аналогичный классу, найденному в документах Apple), необходимо:
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; self.fetchedResultsController.delegate = nil; }
В подробном представлении пользователь изменяет атрибут объекта ChElement и сохраняет проект. DetailView вызывает своего делегата (ChElementTVC) для сохранения. Сохранение выполняется в экземпляре UIManagedDocument, который содержит NSManagedObject.
#define SAVEDOC(__DOC__) [ProjectDocumentHelper saveProjectDocument:__DOC__] // Delegate - (void)chAddElementDetailViewController:(ChDetailViewController *)sender didPressSaveButton:(NSString *)message { SAVEDOC(THE_CURRENT_PROJECT_DOCUMENT); [self.navigationController popViewControllerAnimated:YES]; } // Helper Class + (void)saveProjectDocument:(UIManagedDocument *)targetDocument { NSManagedObjectContext *moc = targetDocument.managedObjectContext; [moc performBlockAndWait:^{ DLog(@" Process Pending Changes before saving : %@, Context = %@", targetDocument.description, moc); [moc processPendingChanges]; [targetDocument saveToURL:targetDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL]; }]; }
Так как делегат (ChElementTVC) выталкивает detailView из стека навигации, вызывается его viewWillAppear и восстанавливается fetchedResultsController.delegate.
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (!self.fetchedResultsController.delegate) { DLog(@"Sleep Now %@", self); //http://mobiledevelopertips.com/core-services/sleep-pause-or-block-a-thread.html [NSThread sleepForTimeInterval:0.1]; DLog(@"Wake up %@", self); [self fetchedResultsControllerWithPredicate:_savedPredicate]; // App Hangs Here ... This is sending messages to CoreData objects. [self.tableView reloadData]; }
Без [NSThread sleepForTimeInterval:0.1];
приложение зависает. Когда я отправляю SIGINT через Xcode, я получаю отладчик и выявляю следующее:
(lldb) бт
* thread #1: tid = 0x1c03, 0x30e06054 libsystem_kernel.dylib semaphore_wait_trap + 8, stop reason = signal SIGINT
frame #0: 0x30e06054 libsystem_kernel.dylib semaphore_wait_trap + 8
frame #1: 0x32c614f4 libdispatch.dylib _dispatch_thread_semaphore_wait$VARIANT$mp + 12
frame #2: 0x32c5f6a4 libdispatch.dylib _dispatch_barrier_sync_f_slow + 92
frame #3: 0x32c5f61e libdispatch.dylib dispatch_barrier_sync_f$VARIANT$mp + 22
frame #4: 0x32c5f266 libdispatch.dylib dispatch_sync_f$VARIANT$mp + 18
frame #5: 0x35860564 CoreData _perform + 160
(lldb) выбор кадра 5
frame #5: 0x35860564 CoreData _perform + 160
CoreData _perform + 160:
-> 0x35860564: add sp, #12
0x35860566: pop {r4, r5, r7, pc}
CoreData -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]:
0x35860568: push {r4, r5, r6, r7, lr}
0x3586056a: add r7, sp, #12
(lldb) разобрать -f
CoreData _perform:
0x358604c4: push {r4, r5, r7, lr}
... snipped ...
0x35860560: blx 0x35938bf4 ; symbol stub for: dispatch_sync_f
-> 0x35860564: add sp, #12
0x35860566: pop {r4, r5, r7, pc}
Возможен другой обходной путь. Кодирование восстановления fetchedResultsController.delegate в -[ChElementTVC viewDidAppear:]
также эффективно задерживает этот параметр в основной очереди.
Дополнительным обходным решением является выполнение всплывающего окна навигации в блоке завершения после завершения сохранения проекта:
#define SAVEDOCWITHCOMPLETION(__DOC__,__COMPLETION_BLOCK__)[ProjectDocumentHelper saveProjectDocument:__DOC__ completionHandler:__COMPLETION_BLOCK__]
void (^completionBlock)(BOOL) = ^(BOOL success) {
[self.navigationController popViewControllerAnimated:YES];
};
SAVEDOCWITHCOMPLETION(THE_CURRENT_PROJECT_DOCUMENT, completionBlock);
Я думаю, что операция сохранения выполняется в фоновом режиме одновременно с восстановлением делегата в основной очереди, но я не знаю, как проверить/доказать/опровергнуть эту теорию.
Итак, может ли кто-нибудь объяснить, что происходит и как лучше всего действовать в этой ситуации? Также приветствуются рекомендации для обучения.