Question

I just "solved" what appears to be a deadlock or synchronization issue with:

    [NSThread sleepForTimeInterval:0.1];

in an app that attaches MPMediaItem (music/images) property references from the IPOD library to object instances, and those objects are back-stored via CoreData. My interest here is to understand exactly what's going on and what is the best practice in this situation. Here goes:

The recipe to replicate this every time is as follows:

  1. User creates a new project.

    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);
        }
    }];
    
  2. Later the managedObjectContext is used to associate the object instances and hydrate the CoreData model

    TheProject *theProject = [TheProject projectWithInfo:theProjectInfo
                                  inManagedObjectContext:doc.managedObjectContext];
    
  3. The user later creates a "CustomAction" object, adds a "ChElement" to it and associates a "MusicElement" with the ChElement. (These are pseudonyms for the CoreData model objects). The MusicElement is added via the IPOD library.

    #define PLAYER [MPMusicPlayerController iPodMusicPlayer]
    
  4. The user saves this project, then switches to an existing project that already has one CustomAction object created, with a ChElement and a MusicElement.

  5. The user selects that ChElement from a tableView and navigates to a detailView. When navigating away from the ChElementTVC (a subclass of a CoreData TableViewController class similar to that found in Apple docs), this is required:

    - (void)viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
        self.fetchedResultsController.delegate = nil;
    }
    
  6. In the detail View, the user changes an attribute of the ChElement object and saves the project. The detailView calls its delegate (ChElementTVC) to do the saving. The save is to the UIManagedDocument instance that holds the 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];
        }];
    }
    
  7. Since the delegate (ChElementTVC) popped the detailView off the navigation stack, its viewWillAppear is called, and the fetchedResultsController.delegate is restored.

    - (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];
    }
    

Without the [NSThread sleepForTimeInterval:0.1]; the app hangs. When I send a SIGINT via Xcode, I get the debugger and reveal the following:

(lldb) bt

    * 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) frame select 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) disassemble -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}

Another work-around is possible. Coding the fetchedResultsController.delegate restoration in -[ChElementTVC viewDidAppear:] also effectively delays this setting on the main queue.

An additional work-around is to execute the nav pop in a completion block after the project saving is finished:

    #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);

I think the save operation runs in the background concurrently with the delegate restoration on the main queue, but I do not know how to examine/prove/disprove that theory.

So, with that, can someone explain what's going on and what is the best practice in this situation? Also, references for study are appreciated.

Was it helpful?

Solution

I ended up implementing the third method, that is, saving the document with a completion block to serialize the transactions interacting with the CoreData store.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top