MagicalRecord save causes EXC_BAD_ACCESS - iphone

I am having issues when I try to do "save" with MagicalRecord. My code:
- (void) findInternetObject {
[InternetObjectFinder runBlockSuccess:^(NSManagedObject *obj) {
obj.attr1 = #"abc";
[[NSManagedObjectContext MR_defaultContext] MR_saveErrorHandler:^(NSError *error) {
NSLog(#"failed to save attr1, Error: %#, %#", error.localizedDescription, error.userInfo);
}];
}];
}
where obj was created in method "runBlockSuccess" method in "InternetObjectFinder" class:
InternetObject *obj = [InternetObject MR_createEntity];
The app crashes at line:
[NSManagedObjectContext MR_defaultContext] MR_saveErrorHandler
with error: EXC_BAD_ACCESS
Any help is appreciated.

It seems to be a scope issue inside your nested blocks,
have you tried to write something like this (not tested):
- (void) findInternetObject {
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
[InternetObjectFinder runBlockSuccess:^(NSManagedObject *obj) {
obj.attr1 = #"abc";
[defaultContext MR_saveErrorHandler:^(NSError *error) {
NSLog(#"failed to save attr1, Error: %#, %#", error.localizedDescription, error.userInfo);
}];
}];
}
If the proble persist maybe this detailed answer can help you:
How do I avoid capturing self in blocks when implementing an API?

You should call save method on main thread. Your code looks that you are saving core data into block. If that doesn't work you can use below code to save.
MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
} completion:^(BOOL success, NSError *error) {
if(success){
NSLog(#"success");
}
}];

Related

Why is my core data not updating?

I update core data in a background thread, like so:
entry.message = [self contentForNoteWithEDML:note.content];
entry.dataLastModified = [NSDate date];
[entry.managedObjectContext save:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[self.tableView reloadData];
});
On each cell of the tableview, it displays a different entry from the fetchedResultsController. On the main thread, I do an NSLog in cellForRowAtIndexPath on the dataLastModified date, and the date doesn't change to the most recent value. If I close the app and run it again, it updates the contents of the cell and the dataLastModified date changes to the correct value.
It seems to be changing the data, as required, but my tableview isn't seeing the changes until the app is restarted. Any ideas why?
EDIT: Doing the NSLog in cellForRowAtIndexPath on a background thread gives the the correct data, but doing it on the main thread does not.
EDIT 2: How my background context works:
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter
addObserver:[AppDelegate applicationDelegate].coreDataManager
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:[AppDelegate applicationDelegate].coreDataManager.managedObjectContext];
NSPersistentStoreCoordinator *journalDataPSC = [AppDelegate applicationDelegate].coreDataManager.managedObjectContext.persistentStoreCoordinator;
dispatch_queue_t addOrUpdateEntriesQueue = dispatch_queue_create("com.App.AddOrUpdateEntries", NULL);
dispatch_async(addOrUpdateEntriesQueue, ^{
NSManagedObjectContext *journalDataMOC = [[NSManagedObjectContext alloc] init];
[journalDataMOC setPersistentStoreCoordinator:journalDataPSC];
//Some code to get me an entry on this context
entry.message = [self contentForNoteWithEDML:note.content];
entry.dataLastModified = [NSDate date];
[entry.managedObjectContext save:nil];
[[NSNotificationCenter defaultCenter] removeObserver:[AppDelegate applicationDelegate].coreDataManager];
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[self.tableView reloadData];
});
});
dispatch_release(addOrUpdateEntriesQueue);
Make sure you're using MOC's correctly, they are not thread-safe and can only be used in the thread they were created in. In this case, if you're doing it right enter.managedObjectContext is different from the MOC of the fetched results controller (which is in the main thread).
That means saves in the background do not necessarily get propagated to he main thread MOC. Make sure you handle NSManagedObjectContextDidSaveNotification in the main thread by adding an observer when you create the fetched results controller.
Looking at your code here are a few points that I notice:
You should register for the save notification thrown by the background MOC not the main thread MOC.
You should initialize your background MOC with initWithConcurrencyType: using NSPrivateQueueConcurrencyType
Once you're using NSPrivateQueueConcurrencyType it's much better to use performBlock: to make your changes and save instead of using low-level GCD dispatch methods.
Even though I don't know anything about your coreDataManager object your notification registration is wrong. The object you want to observe is your background managed object context journalDataMOC, not your coreDataManager.
So this should work, but notice that you have to move the registration inside your addOrUpdateEntriesQueue:
[notificationCenter
addObserver:[AppDelegate applicationDelegate].coreDataManager
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:journalDataMOC];
But all in all you should use the CoreData API to do this work (like Engin said) as it is much cleaner. So remove all the GCD and notification stuff and use this snippet (not tested):
NSManagedObjectContext *mainContext = [[[AppDelegate applicationDelegate] coreDataManager] managedObjectContext];
NSManagedObjectContext *journalDataMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[journalDataMOC setParentContext:mainContext];
[journaDataMOC performBlock:^{
//Some code to get me an entry on this context
entry.message = [self contentForNoteWithEDML:note.content];
entry.dataLastModified = [NSDate date];
[journalDataMOC save:nil];
[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSError *error;
[mainContext save:&error];
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[self.tableView reloadData];
}];
}];
When the journalDataMOC saves it will "push" the changes up to the paren context (mainContext).
Note that your [[[AppDelegate applicationDelegate] coreDataManager] managedObjectContext] has to be initialized with type NSMainQueueConcurrencyType. Also note that you have to save your mainContext here or at some time in the future to persist your changes to the database.

Objective-C GCD wait for Block to finish (AFNetworking)

I've been trying to experiment with some code from a tutorial, however not having much success due to not getting my head around GCD.
I have an class named API.m and here is the code regarding GCD:
+ (API *) sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
});
return sharedInstance;
}
-(void)commandWithParams:(NSMutableDictionary*)params
onCompletion:(JSONResponseBlock)completionBlock
{
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST"
path:APIPath
parameters:params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
I make a simple test by implementing a button and getting an NSArray to print it's content to the output window:
- (IBAction)test:(id)sender {
NSMutableDictionary* params =[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"pending", #"command",
[[[API sharedInstance] user] objectForKey:#"UserID"] , #"userID",
nil];
[[API sharedInstance] commandWithParams:params
onCompletion:^(NSDictionary *json) {
//result returned
if ([json objectForKey:#"error"]==nil) {
// Simple example
[self.users addObject:#"1"];
} else {
//error
[UIAlertView title:#"Error" withMessage:[json objectForKey:#"error"]];
}
}];
NSLog(#"%#", self.users);
}
Now when I first click the button an empty NSArray is printed to the output window, but when I press it again it print's "1". It's clear that the program is reaching NSLog before the completion block has time to fully execute. Could someone please help me modify the code so that I have the option to have the NSLog execute after the completion block has finished?
Not sure as to what you are trying to accomplish, but if the goal is to just have NSLog execute after the completion block, you can move the NSLog statement after
[self.users addObject:#"1"];
If you have some code which you want to execute after adding it to the array, you can have
[self methodName]; in the completion block and it will get called there.
Completion block, is the code which is run after execution of the code which you wanted run. The code which you wanted run, will happen asynchronously and on another thread. After that code is run, the completion block code will get executed.

Xcode, ensure codes after blocks run later for NSURLConnection asynchronous request

Hi there: I have been writing an iOS program which uses many http queries to the backend rails server, and hence there are tons of codes like below. In this case, it is updating a UITableView:
//making requests before this...
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse* response, NSData* data, NSError* error)
{
NSLog(#"Request sent!");
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"Response code: %d", [httpResponse statusCode]);
if ([data length] > 0 && error == nil){
NSLog(#"%lu bytes of data was returned.", (unsigned long)[data length]); }
else if ([data length] == 0 &&
error == nil){
NSLog(#"No data was returned.");
}
else if (error != nil){
NSLog(#"Error happened = %#", error); }
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (jsonObject != nil && error == nil){
NSLog(#"Successfully deserialized...");
if ([jsonObject isKindOfClass:[NSDictionary class]]){
NSDictionary *deserializedDictionary = (NSDictionary *)jsonObject;
NSLog(#"Dersialized JSON Dictionary = %#", deserializedDictionary);
[listOfItems addObject:deserializedDictionary];
}
else if ([jsonObject isKindOfClass:[NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(#"Dersialized JSON Array = %#", deserializedArray);
[listOfItems addObjectsFromArray:deserializedArray];
}
else {
/* Some other object was returned. We don't know how to deal
with this situation as the deserializer only returns dictionaries
or arrays */ }
}
else if (error != nil){
NSLog(#"An error happened while deserializing the JSON data., Domain: %#, Code: %d", [error domain], [error code]);
}
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}];
//the place where never runs
NSLog(#"End of function.");
Here is the problem: the last line gets executed usually before the code block. How do I ensure that the code after block actually runs after the block?
I am aware that the block uses some other threads, which is why I use performSelectorOnMainThread function instead of a direct call of [self.tableView reloadData]. But if I want to do something else afterward, how am I supposed to do?
Also, can anyone show some better ways to do this? I am trying to figure out the best way to make massive calls to the backend. There are several ways to make asynchronous requests, including this block way and another old-fashioned way invoking delegate classes. In the progress to refactor the codes, I also tried to create my own delegate class and let other classes invoke that, but it is difficult to identify the correct behaviour of callback functions for which connection's data it returns, especially for classes that use multiple functions to call different requests. And I don't want to use synchronous calls.
Thanks very much for any answers. Also welcome to point out any bugs in the code.
You can using dispatch group
Sample code:
- (void)doSomethingAndWait {
// synchronous method
// called in main thread is not good idea.
NSAssert(! [NSThread isMainThread], #"this method can't run in main thread.");
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
//making requests before this...
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse* response, NSData* data, NSError* error)
{
// your work here.
dispatch_group_leave(group);
}];
// wait for block finished
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
//will call until block is finished.
NSLog(#"End of function.");
}
And to call that method, you need avoid call it in main thread.
you should call it like this
dispatch_queue_t queue = dispatch_queue_create("com.COMPANYNAME.APPNAME.TASKNAME", NULL);
dispatch_async(queue, ^{
[self doSomethingAndWait];
});

saving managedObjectContext returns null error

Could anyone see any possible reasons why?
Friend *newFriend = [NSEntityDescription insertNewObjectForEntityForName:#"Friend" inManagedObjectContext:managedObjectContext];
newFriend.name = #"Jim";
newFriend.age = [NSNumber numberWithInt:5];
NSError *error = nil;
if ([managedObjectContext save:&error])
{
NSLog(#"error %#", error);
}
managedObjectContext was passed to the view controller where this code is from the application delegate.
Thanks
if (![managedObjectContext save:&error])
{
NSLog(#"error %#", error);
}
that should be
You should expect error to continue to be nil if save: succeeded (and therefore returned YES as you're testing). What behavior did you expect here?

"[CFString release]: message sent to deallocated instance" when using CoreData

I have started using CoreData in my project. Without the pieces of code from CodeData my project was working pretty fine. I added the methods for accessing the NSManagedObjectContext from the coreData project template. Now I try to create new CoreData object with the following code:
- (void)saveSearchResultToHistory:(NSArray *) productsArray {
[productsArray retain];
NSLog(#"context: %#", self.managedObjectContext);
Product *product = [NSEntityDescription
insertNewObjectForEntityForName:#"Product"
inManagedObjectContext:self.managedObjectContext];
product.productId = [(Product *) [productsArray objectAtIndex:0] productId];
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[productsArray release];
}
When this method is ran once then everything is fine, when I try to run it for the second time, the processing is stopped at:
Product *product = [NSEntityDescription
insertNewObjectForEntityForName:#"Product"
inManagedObjectContext:self.managedObjectContext];
with the following error message in the console:
[CFString retain]: message sent to deallocated instance 0x5a23b0
Any ideas what might be wrong?
Thanks!
First off you do not need to save the context every time you add something, just save when the app closes or goes in the background.
The error you are getting looks like you over release a NSString some where.
To check if the error isn't in the coredata context use this save function:
- (void)saveContext {
if ([self.managedObjectContext hasChanges]) {
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
dbgPrint(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if (detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
dbgPrint(#"--DetailedError: %#", [detailedError userInfo]);
}
} else {
dbgPrint(#" %#", [error userInfo]);
}
}
}
}