Crash when try to save an object inside a block. (CoreData could not fulfill a fault for...) - objective-c

I'm trying to save an object inside the AFNetworking block, but SOMETIMES it crashes with error: CoreData could not fulfill a fault for...
I've read that it's related when I try to manage an object data when it was deleted from the persistent store.
(https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdTroubleshooting.html)
How can I avoid this?
Example:
Line *line = [Line MR_createEntity];
[line setSync:#(SYNC_PROCESSING)];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://test.com/request" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// CRASH
[line setSync:#(SYNC_SUCCESS)];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
} failure:nil];

You're trying to perform a background operation. The defaultContext is for the main thread only (it's not clearly documented, we're working on that). But what you want to do is perform your save in a context that will handle the background thread operations for you. That is, you want to use a NSManagedObjectContext that works on the background. In general try this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
id myObject = [line MR_inContext:localContext];
[myObject setSync:#(value)];
}];
This will let MagicalRecord create the proper background context for your data to operate on when it tries to save data. MagicalRecord will set up much of the cruft needed in order to start using Core Data. Just let it do much of the work and you'll be on your way.

Related

Background (Asynchronous) programming in Cocoa/Objective-C

I want something similar to C#'s BackgroundWorker.
I want to execute a block code of code (irrespective of what kind of code it is. It could be networking, I/O, complex math operations, whatever) in the background while I animate a NSProgressIndicator. Once the operation is complete, not only do I want to hide the progress indicator but I also want to receive result object(s) from the background code back to the main code.
What is the best way to do this?
Thanks for the help!
You may want to do some reading about NSOperation, NSOperationQueue, and blocks.
Using these you can execute your "block" of code asynchronously and have it "schedule" an update of the progress indicator on the main thread every so often. You can set a completion block (callback) to execute when the operation is complete and it can call a function to be executed on the main thread.
This article provides a nice overview of the options with small examples.
This answer provides an example for defining and using a completion block.
I think this library would be helpful - https://github.com/AFNetworking/AFNetworking
This library is a wrapper for Apple's API and has async tasks handled correctly. The example is shown here by Matt Thompson
How to download a file and save it to the documents directory with AFNetworking?
how to use API for downloading a resource from a url in background. This is just one example but most of them work in a similar manner to perform task in background without blocking Main UI thread
Eg :-
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"..."]];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"filename"];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation start];

How does the save method in Core Data works?

I'm following a guid in core data, and they implement an action method to preform saving to the database using a ManagedObject. I understand all the code in the method except the method which they say preform the saving, and to me it looks like the method checks if there is an error, and if yes so there is an NSLog to print that there was an error. this is the method:
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
// creating a new managed object
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Device" inManagedObjectContext:context];
[newDevice setValue:self.nameTextField.text forKey:#"name"];
[newDevice setValue:self.versionTextField.text forKey:#"version"];
[newDevice setValue:self.companyTextField.text forKey:#"company"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Obviously something happens in [context save:&error] this call which I'd love if you can explain what?
Calling save: persists changes made to the object graph on the specific context and takes it one level above.
Each context contains its own changeset, and when you call save:, the changes are either taken one level above (to its parent context), or, if there is no parent context, to the store coordinator to be persisted by the method specified when opening the coordinator (SQLite, XML, binary, etc.).
Changes can be modifications, insertions or deletions.
Before saving, changes to objects are verified and objects are notified about the save process.
After saving, notifications are sent to the system to let know various components (such as fetch results controllers, your code, etc.) that the save operation has taken place.

EXC_BAD_ACCESS when doing a POST using AFNetworking 2.0

My code:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager] ;
manager.requestSerializer = [AFJSONRequestSerializer serializerWithWritingOptions:NSJSONReadingAllowFragments];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager POST:[_urlBase stringByAppendingPathComponent:_urlRequest]
parameters:paramDictionary
success:^(NSURLSessionDataTask *task, id responseObject){
dispatch_async(dispatch_get_main_queue(),^{
[self AFRequestFinished:responseObject];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error){
NSLog(#"JSON ERROR PARAMETERS: %#", error);
}
];
I am using this POST request to send several types of data up to a server along with pictures. I am using something very similar for the GET request and it works fine. Whenever I run this code I get a EXC_BAD_ACCESS CODE=1 error on the following line of AFNetworking 2.0. The responseObject is 0x0:
responseObject = [self.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError];
The above line of code is within the if/else method in:
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
UPDATE
I ran instruments on the code, and it there is a zombie present. AFNetworking is trying to make a call to the NSError, but it has been deallocated. I believe this has arisen because the POST call initially succeeds, but there is still an error that is flagged. So it initially thinks there is no error and sets it to nil, but then tries to call for it in the error block of the POST.
If you're using the most recent version, you may be experiencing this known issue when the JSON serializer returns an error. You can work around this until a new release is made by:
removing the #autoreleasepool in the serializer, or
changing the scope of the error to outside the autorelease pool
(Both solutions are outlined in the issue linked above.)
On a side note, there's no need to dispatch to the main queue in the completion handler. AFNetworking guarantees that completion blocks are called on the main thread.

Cancelling NSManagedObjectContext performBlock

I am using parent/child concurrency pattern to import large data chunks. Importing is performed in the background without blocking the main thread, like this:
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = mainMOC;
[temporaryContext performBlock:^{
// import data …
// push to parent
NSError *error;
if (![temporaryContext save:&error]) {
// handle error
}
// save parent to disk asynchronously
[mainMOC performBlock:^{
NSError *error;
if (![mainMOC save:&error]) {
// handle error
}
}];
}];
Everything works great, but what if I need to cancel data import? Is there any way to cancel performBlock?
No - blocks and other any synchronous operation can not implicitly be cancelled.
you have to program it to be cancellable
e.g. here maybe... split the performBLock up into N calls That each only do little work.
If anyone else has the same problem my solution was to use two independent managed object contexts which are both connected to the same persistent store coordinator. The managed object context that does the heavy lifting is encapsulated in a NSOperation subclass. NSOperation can be canceled at any point. Here is a link to the example provided by Apple.

NSSortDescriptor of NSFetchRequest not working after context save

I'm doing operations in a GCD dispatch queue on a NSManagedObjectContext defined like this:
- (NSManagedObjectContext *)backgroundContext
{
if (backgroundContext == nil) {
self.backgroundContext = [NSManagedObjectContext MR_contextThatNotifiesDefaultContextOnMainThread];
}
return backgroundContext;
}
MR_contextThatNotifiesDefaultContextOnMainThread is a method from MagicalRecord:
NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[NSManagedObjectContext MR_defaultContext]];
return context;
After fetching my objects and giving them the correct queue position i log them and the order is correct. However, the second log seems to be completely random, the sort descriptor clearly isn't working.
I have narrowed down the Problem to [self.backgroundContext save:&error]. After saving the background context sort descriptors are broken.
dispatch_group_async(backgroundGroup, backgroundQueue, ^{
// ...
for (FooObject *obj in fetchedObjects) {
// ...
obj.queuePosition = [NSNumber numberWithInteger:newQueuePosition++];
}
NSFetchRequest *f = [NSFetchRequest fetchRequestWithEntityName:[FooObject entityName]];
f.predicate = [NSPredicate predicateWithFormat:#"queuePosition > 0"];
f.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"queuePosition" ascending:YES]];
NSArray *queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
for (FooObject *obj in queuedObjects) {
DLog(#"%# %#", obj.queuePosition, obj.title);
}
if ([self.backgroundContext hasChanges]) {
DLog(#"Changes");
NSError *error = nil;
if ([self.backgroundContext save:&error] == NO) {
DLog(#"Error: %#", error);
}
}
queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
for (FooObject *obj in queuedObjects) {
DLog(#"%# %#", obj.queuePosition, obj.title);
}
});
I've got no idea why the sort descriptor isn't working, any Core Data experts want to help out?
Update:
The problem does not occur on iOS 4. I think the reason is somewhere in the difference between thread isolation and private queue modes. MagicalRecord automatically uses the new concurrency pattern which seems to behave differently.
Update 2:
The problem has been solved by adding a save of the background context:
if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
DLog(#"Changes");
NSError *error = nil;
if ([[NSManagedObjectContext MR_contextForCurrentThread] save:&error] == NO) {
DLog(#"Error: %#", error);
} else {
NSManagedObjectContext *parent = [NSManagedObjectContext MR_contextForCurrentThread].parentContext;
[parent performBlockAndWait:^{
NSError *error = nil;
if ([parent save:&error] == NO) {
DLog(#"Error saving parent context: %#", error);
}
}];
}
}
Update 3:
MagicalRecord offers a method to recursively save a context, now my code looks like this:
if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
DLog(#"Changes");
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveWithErrorHandler:^(NSError *error) {
DLog(#"Error saving context: %#", error);
}];
}
Shame on me for not using it in the first place...
However, I don't know why this helps and would love to get an explanation.
I'll try to comment, since I wrote MagicalRecord.
So, on iOS5, MagicalRecord is set up to try to use the new Private Queue method of multiple managed object contexts. This means that a save in the child context only pushes saves up to the parent. Only when a parent with no more parents saves, does the save persist to its store. This is probably what was happening in your version of MagicalRecord.
MagicalRecord has tried to handle this for you in the later versions. That is, it would try to pick between private queue mode and thread isolation mode. As you found out, that doesn't work too well. The only truly compatible way to write code (without complex preprocessor rules, etc) for iOS4 AND iOS5 is to use the classic thread isolation mode. MagicalRecord from the 1.8.3 tag supports that, and should work for both. From 2.0, it'll be only private queues from here on in.
And, if you look in the MR_save method, you'll see that it's also performing the hasChanges check for you (which may also be unneeded since the Core Data internals can handle that too). Anyhow, less code you should have to write and maintain...
The actual underlying reason why your original setup didn't work is an Apple bug when fetching from a child context with sort descriptors when the parent context is not yet saved to store:
NSSortdescriptor ineffective on fetch result from NSManagedContext
If there is any way you can avoid nested contexts, do avoid them as they are still extremely buggy and you will likely be disappointed with the supposed performance gains, cf. also:
http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains
Since CoreData isnot a safe-thread framework and for each thread(operation queue), core data uses difference contexts. Please refer the following excellent writing
http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/