Search code examples
objective-cios6

How to use coordinateWritingItemAtURL in iOS 6.1.3 to save coredata document using iCloud?


I am using the coredata with iCloud in my app. I am using couple of code which is working fine below the iOS 6.1.3 but not write the document and save when using iOS 6.1.3 devices.

I am using the OSX 10.8.3 and xcode version:4.6.2

The execution stop at the code given below when using iOS 6.1.3 -

[coordinator coordinateWritingItemAtURL:[managedDoc.persistentStoreOptions valueForKey:NSPersistentStoreUbiquitousContentURLKey] options:NSFileCoordinatorWritingForDeleting error:&logerror byAccessor:^(NSURL *newURL) {
    NSError * delError = nil;
    [[NSFileManager defaultManager] removeItemAtURL:newURL error:&delError];
    //if(delError)
        //NSLog(@"Error deleting transaction file .... , reason : %@",delError.localizedDescription); 
}];

The whole code is given below:

-(void)saveManagegDocument{

if(iCloud){
NSError * error = nil;
[coordinator coordinateWritingItemAtURL:managedDoc.fileURL options:NSFileCoordinatorWritingForDeleting error:&error byAccessor:^(NSURL *newURL) {
    NSError * delError = nil;
    [[NSFileManager defaultManager] removeItemAtURL:newURL error:&delError];
    //if(delError)
        //NSLog(@"Error deleting data file .... , reason : %@",delError.localizedDescription); 
}];
NSError * logerror = nil;

[coordinator coordinateWritingItemAtURL:[managedDoc.persistentStoreOptions valueForKey:NSPersistentStoreUbiquitousContentURLKey] options:NSFileCoordinatorWritingForDeleting error:&logerror byAccessor:^(NSURL *newURL) {
    NSError * delError = nil;
    [[NSFileManager defaultManager] removeItemAtURL:newURL error:&delError];
    //if(delError)
        //NSLog(@"Error deleting transaction file .... , reason : %@",delError.localizedDescription); 
}];
}
[managedDoc saveToURL:managedDoc.fileURL 
     forSaveOperation:UIDocumentSaveForCreating 
    completionHandler:^(BOOL success) {
        if (success) {
            [managedDoc closeWithCompletionHandler:^(BOOL success) {
                [managedDoc openWithCompletionHandler:^(BOOL success) {
                    [self performSelectorOnMainThread:@selector(documentReady) withObject:nil waitUntilDone:NO];
                }];
            }];
        }
        else{
            [[[UIAlertView alloc] initWithTitle:@"Could not save or open core data database." message:nil delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil] show];
           // [self showMessage:@"Could not save or open core data database "];
            //NSLog(@"Could not save or open core data database ");
        }
    }];

}

Anybody please help me.

And Thanks in advance.


Solution

  • See that documentation about deleting and you will see that NSFileManager is not thread-safe, so you want to alloc/init a fresh instance of it when you use it to call removeItemAtURL. That is likely causing deadlock where you see execution stops.

    You'll also want to be sure to run this code off the main thread. I use GCD to do that. My method to delete a document would look like...

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
       NSAssert(![NSThread isMainThread], @"Must be not be on main thread");
       // insert code here 
    });
    

    When you work with async blocks it is best to provide a completion block which can be executed once the work in the async block is completed so you can safely start the next action. I see you are doing multiple actions in sequence even though they are async actions. It would be best to break these into distinct methods which are then coordinated with completion blocks. Here is a sample method to delete a document.

    - (void)deleteDocument:(UIDocument *)document withCompletionBlock:(void (^)())completionBlock {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    
            NSError *fileCoordinatorError = nil;
    
            [[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:document.fileURL options:NSFileCoordinatorWritingForDeleting error:&fileCoordinatorError byAccessor:^(NSURL *newURL) {
    
                // extra check to ensure coordinator is not running on main thread
                NSAssert(![NSThread isMainThread], @"Must be not be on main thread");
    
                // create a fresh instance of NSFileManager since it is not thread-safe
                NSFileManager *fileManager = [[NSFileManager alloc] init];
                NSError *error = nil;
                if (![fileManager removeItemAtURL:newURL error:&error]) {
                    NSLog(@"Error: %@", error);
                    // TODO handle the error
                }
    
                if (completionBlock) {
                    completionBlock();
                }
            }];
        });
    }
    

    Then you can deleteDocument and give it a completion block which will be executed after the document has been deleted. You have to be careful with all this concurrency happening.

    Once the work in this async method has finished it can start here.

    [self deleteDocument:document withCompletionBlock:^{
        // insert code for next action
    }];
    

    In theory the NSFileCoordinator would handle that, but maybe it is deadlocking on [NSFileManager defaultManager] in your code.

    Let me know how this works out for you. I have been working on a Document-Based app and it has been a real challenge because there are things happening automatically and I cannot make sense of all of it yet. But since I just figured out this deadlocking issue I could at least share this snippet of code with you.