1

With Zombies enabled, I'm getting the error in the title (message sent to deallocated instance of NSError) on the following saveToURL call:

[aDocument saveToURL:aDocument.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success) { ...

Stack trace looks like the following:

enter image description here

aDocument is an instance of a subclass of UIManagedDocument. I have concurrency debug on and I've looked to see if I have any threading conflicts, haven't been able to find any yet. How can I debug this?

EDIT: Also tried the following code with the same crash occurring

__weak typeof(self) weakSelf = self;

    [aDocument saveToURL:aDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
        if (success) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf documentSaved:aDocument forRestoredAssessment:patientAssessment];
            });
        }
    }];

EDIT: bounty added

Bradley Thomas
  • 4,060
  • 6
  • 33
  • 55
  • 1
    What are you doing inside the completionHandler? – ppalancica Dec 09 '16 at 04:23
  • This method is called... [self documentSaved:aDocument forRestoredAssessment:patientAssessment]; which does a huge bunch of stuff. – Bradley Thomas Dec 09 '16 at 13:39
  • 1
    In general it's not safe to touch ANYTHING in UIKit or directly related to UIKit from any thread that is not the main queue. I would in fact assume that's the core of the issue. – Mgetz Feb 01 '17 at 21:12
  • Is `aDocument` a local variable or a property? – Sulthan Feb 01 '17 at 22:38
  • @Sulthan local variable (method parameter) – Bradley Thomas Feb 02 '17 at 14:06
  • your error seems to be that aDocument (or whatever is holding your aDocument) has already been deallocated. performing on background thread? – Joshua Feb 06 '17 at 03:11
  • 1
    @BradThomas I noticed this line in the UIDocument documentation: `Instead, in this case, the error is available to your app in the handleError:userInteractionPermitted: method and in the UIDocumentStateChangedNotification notification.` here: https://developer.apple.com/reference/uikit/uidocument/1619978-contentsfortype?language=objc. Maybe if you listen for this notification you could potentially figure out whats going on with the NSError. – Prientus Feb 08 '17 at 20:11
  • Thanks @Prientus, I think I figured this out already though, see the answer I posted below – Bradley Thomas Feb 09 '17 at 16:37

3 Answers3

2

We have discovered that this zombie occurs when the parent context has a merge conflict. If you override [UIManagedDocument writeContents:toURL:forSaveOperation:originalContentsURL:error] method in your subclass, access the parent context with:NSManagedObjectContext *context = [(NSDictionary *)contents objectForKey:@"parentContext"];. Save the parent context if changes exist using the standard code as below. If the conflict is resolved by either setting a merge policy or reset the context then the zombie will not occur.

- (BOOL)writeContents:(id)contents
                toURL:(NSURL *)url
     forSaveOperation:(UIDocumentSaveOperation)saveOperation
  originalContentsURL:(NSURL *)originalContentsURL
                error:(NSError * _Nullable __autoreleasing *)outError {

    NSError *error = nil;

    NSManagedObjectContext *context = self.managedObjectContext.parentContext;

    if (context.hasChanges) {
        [context performBlockAndWait:^{

            NSError *saveError = nil;
            if (![context save:&saveError]) {
                NSLog(@"Document Writing: error saving context %@", saveError);
                [context reset];
            }
        }];
    }
    return [super writeContents:contents
                          toURL:url
               forSaveOperation:saveOperation
            originalContentsURL:originalContentsURL
                          error:outError];
}
James Newbould
  • 136
  • 1
  • 7
0

I believe I figured out what was causing this, since I made the following change and then the error went away. The error was tricky to resolve though since it did not directly point to this being the cause.

I was using UIDocumentSaveForOverwriting but I discovered that sometimes the file had already been deleted by another process.

So to fix the issue I tested for file existence and then used either UIDocumentSaveForOverwriting or UIDocumentSaveForCreating based on that.

Bradley Thomas
  • 4,060
  • 6
  • 33
  • 55
-1

It seems that the object that calls the completion block code may not be around at that time, so you need some way to keep that alive.

Try this code:

__weak typeof(self) weakSelf = self;

dispatch_async(dispatch_get_main_queue(), ^{
    [weakSelf documentSaved:aDocument forRestoredAssessment:patientAssessment]; 
});

Let me know if you still have the problem.

ppalancica
  • 4,236
  • 4
  • 27
  • 42