I think I lost my Managed Object Context - iphone

So I'm working on a clone of CoreDataBooks.
It's a little different. When the '+' button is pushed, it launches a navController, containing 2 views. The first (AddPatientVC) asks for name of the Patient then its pushed to a 2nd View Controller (AddPatientDetailVC) which asks for more detailed information. It's the 2nd view controller that I've got the delegate set up with, not the first, like in CoreDataBooks.
For some reason, when the delegate method is fired, the notification method doesn't get fired, so I've somehow lost track of my MOC, either the specific MOC for adding a new Patient.
The specific error i get is:'+entityForName: could not locate an NSManagedObjectModel for entity name 'Patient''
Here's my code - addPatient, delegate method and notification method. Any suggestions on simplification would be appreciated. Thanx
-(void)addPatient:(id)sender
{
PatientAddViewController *patientAddViewController = [[PatientAddViewController alloc] initWithNibName:#"PatientAddViewController" bundle:nil];
PatientAddDetailViewController *patientAddDetailViewController = [[PatientAddDetailViewController alloc] initWithNibName:#"PatientAddViewController" bundle:nil];
patientAddDetailViewController.delegate = self;
//Create a new MOC for adding a book
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addPatientManagedObjectContext = addingContext;
[addingContext release];
[addPatientManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
patientAddViewController.patient = (Patient *)[NSEntityDescription insertNewObjectForEntityForName:#"Patient" inManagedObjectContext:addingContext];
//patientAddViewController.addPatientManagedObjectContext = self.addPatientManagedObjectContext;
UINavigationController *addingNavController = [[UINavigationController alloc] initWithRootViewController:patientAddViewController];
[self.navigationController presentModalViewController:addingNavController animated:YES];
[addingNavController release];
[patientAddViewController release];
}
- (void)patientAddDetailViewController:(PatientAddDetailViewController *)controller didFinishWithSave:(BOOL)save
{
NSLog(#"Delegate Method fired");
if (save)
{
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
//The notification isn't firing becuase addPatientManagedObjectContext is null for some reason
[dnc addObserver:self selector:#selector(addControllerContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:addPatientManagedObjectContext];
NSError *error;
//if (![patient.managedObjectContext save:&error])
if (![addPatientManagedObjectContext save:&error])
{
NSLog(#"Before Error");
//Handle the error...
NSLog(#"Unresolved Error %#, %#",error, [error userInfo]);
exit(-1);//Fail
NSLog(#"After Error");
}
[dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:addPatientManagedObjectContext];
}
self.addPatientManagedObjectContext = nil;
[self.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
}
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
NSLog(#"Save Notification Fired");
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
}

It looks like you create the context, and store it in self
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addPatientManagedObjectContext = addingContext;
[addingContext release];
But then you call the "add" method on the other controller:
patientAddViewController.patient = (Patient *)[NSEntityDescription
insertNewObjectForEntityForName:#"Patient" inManagedObjectContext:addingContext];
(remember, you released 'addingContext' up above, 'addingContext' is not guaranteed to contain anything valid at this point)
Looks like you should be passing self.addPatientManagedObjectContext rather than addingContext in your insertNewObjectForEntityForName:#"Patient" line.

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.

Update UI after requestAccessToAccountsWithType

I'm developing an app to help me understand OBJECTIVE-X/OSX.
The app simply connects to Facebook and sends a notification using NSUserNotification.
It is working fine, but now I want to add some UI to the mix.
To make the example simpler, I want to update a label (NSTextField) to show the status of the Facebook connection.
Connecting…
Connected
Failed
I have the following code in one File FacebookRequest.m
- (void) connectFacebook{
if(self.account == nil){
self.account = [[ACAccountStore alloc]init];
}
ACAccountType *facebookAccount = [self.account
accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *options = #{
ACFacebookAppIdKey: #"MY_CODE",
ACFacebookPermissionsKey: #[#"email",
#"user_about_me",
#"user_likes",
#"manage_notifications",
#"user_activities"],
ACFacebookAudienceKey: ACFacebookAudienceFriends
};
[self.account requestAccessToAccountsWithType:facebookAccount
options:options
completion:^(BOOL success, NSError *error){
if(success){
NSArray *accounts = [self.account accountsWithAccountType:facebookAccount];
self.account = [accounts lastObject];
}
else{
NSLog(#"Erro %#", [error description]);
}
}];
}
and the following one in my AppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self.statusFacebook setStringValue:#"Connecting…"];
FacebookRequest *request = [[FacebookRequest alloc]init];
[request connectFacebook];
}
What is the best way to update the UI after the request is complete and I have an account?
I'm having troubles since the request is asynchronous and I can't return any value inside the requestAccessToAccountsWithType block. Another point is that if I put some "ifs" to check if my account is nil after it, it will be executed before the block has finished executing, so the account would still be nil.
Thanks!
PS.: Sorry for the English if it is not clear enough.
You may use NSNotificationCenter for this purpose:
[self.account requestAccessToAccountsWithType:facebookAccount
options:options
completion:^(BOOL success, NSError *error){
if(success){
NSArray *accounts = [self.account accountsWithAccountType:facebookAccount];
self.account = [accounts lastObject];
// You post a notification that the UI should update here
[[NSNotificationCenter defaultCenter] postNotificationName:#"UpdateUI" object:nil];
}
else{
NSLog(#"Erro %#", [error description]);
}
}];
Then, you add your viewController that should update its UI as an observer of this notification:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateUI) name:#"UpdateUI" object:nil];
}
- (void)updateUI {
// Here you actually update your UI
}
p.s. if you are not using arc you also remove the observer in dealloc:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];

How to refresh after updating core data value in table view cell

I'm using core data in my app.I have an two view controller one viewcontroller show the list of book names after selecting anyone of the book move to second view controller for using the update button to update the value as 1 that will be show in core data table.Now back to main viewcontroller and after selecting same book doesnot show the value in secondviewcontroller but core data table having a value as 1. how to reload main viewcontroller in table view with core data table value .plz anyone known help me.
Here the source code below
- (void)viewDidLoad
{
[super viewDidLoad];
if(dictionary!=nil)//net is not connected fetch core data to display the booknames
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#" Warning Message" message:#"You are not connect to internet so becoz of loading default values" delegate:self cancelButtonTitle:#"ok" otherButtonTitles:nil, nil];
[alert show];
[self fetchValueFromDatabase];
}
}
-(void)fetchValueFromDatabase
{
NSFetchRequest *fech=[[NSFetchRequest alloc]init];
NSEntityDescription *entity=[NSEntityDescription entityForName:#"Bookname" inManagedObjectContext:self.managedObjectContext];
[fech setEntity:entity];
NSError *savingError=nil;
NSArray *allBook=[self.managedObjectContext executeFetchRequest:fech error:&savingError];
NSLog(#"Array count value:%i",allBook.count);
coreBookName=[allBook valueForKey:#"bookName"];
NSLog(#"all value:%#",coreBookName);
for(NSString *key in coreBookName)
{
NSLog(#"Key value:%#",key);
[insertBookname addObject:key];
}
coreVoteCount=[allBook valueForKey:#"vote"];
NSLog(#"All vote value:%#",coreVoteCount);
for(NSString *key in coreVoteCount)
{
[bookCount addObject:key];
NSLog(#"Key value:%#",key);
}
for(NSArray *key in allBook)
{
[addItems addObject:key];
NSLog(#"All items:%#",key);
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableview1 reloadData];
}
call your below method from (void)viewWillAppear:method
[self fetchValueFromDatabase];//call this from ViewWillAppear
Let me know it is working or not...
Happy Coding.!!!!
You are calling this in viewDidLoad [self fetchValueFromDatabase]; and [self.tableview1 reloadData]; this in viewWillAppear that is why you are not getting any updates in your table.
What you should do is call [self.tableview1 reloadData]; at the end of your function fetchValueFromDatabase
-(void)fetchValueFromDatabase
{
NSFetchRequest *fech=[[NSFetchRequest alloc]init];
NSEntityDescription *entity=[NSEntityDescription entityForName:#"Bookname" inManagedObjectContext:self.managedObjectContext];
[fech setEntity:entity];
NSError *savingError=nil;
........................
.......................
.......................
[self.tableview1 reloadData];
}

Bad Access on Core Data deleteObject

I could use some assistance in debugging a EXC_BAD_ACCESS error received on the [context deleteObject:loan]; command. The error is received in the following delegate method:
- (void)didCancelNewLoan:(Loan *)loan {
// save the context
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:loan]; // *** EXC_BAD_ACCESS here ***
// This method is called from a the following method in a second class:
- (IBAction)cancel:(id)sender {
[delegate didCancelNewLoan:self.loan];
}
// The loan ivar is created by the original class
// in the below prepare for Segue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"NewLoan"]) {
UINavigationController *navController = (UINavigationController *)[segue destinationViewController];
LoanViewController *loanView = (LoanViewController *)[[navController viewControllers] lastObject];
loanView.managedObjectContext = self.managedObjectContext;
loanView.delegate = self;
loanView.loan = [self createNewLoan];
loanView.newLoan = YES;
}
// Finally, the loan is created in the above
// method's [self createNewLoan] command:
- (NSManagedObject *)createNewLoan {
//create a new instance of the entity managed by the fetched results controller
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef uuidstring = CFUUIDCreateString(NULL, uuid);
//NSString *identifierValue = (__bridge_transfer NSString *)uuidstring;
[newManagedObject setValue:(__bridge_transfer NSString *)uuidstring forKey:#"identifier"];
CFRelease(uuid);
CFRelease(uuidstring);
NSError *error;
[self.fetchedResultsController performFetch:&error];
NSLog(#"%i items in database", [[self.fetchedResultsController fetchedObjects] count]);
return newManagedObject;
}
Appreciate your looking at the above methods.
Guess #1: you are accessing a deallocated object. To debug: turn on zombies and see what happens.
Update: here's how you turn on zombies in Xcode 5:
Product > Scheme > Edit Scheme, select Diagnostics tab, check "Enable Zombie Objects"
for older Xcode
, edit your build settings, add and enable these arguments in your build scheme:
Guess #2: you have a multithreaded app and you are accessing a managed object context from different threads, which is a no no.
You can add an assert before your delete:
assert( [ NSThread isMainThread ] ) ;
From looking at your code above, there's nothing that stands out as being done incorrectly.
I am wondering whether you are dealing with two different managed object contexts without realising it? You will have to set some breakpoints where you create the Loan object and see if that might be the case.
Also why do you have to get a reference to the context via fetchedResultsController if you already have a declared property for it in self.managedObjectContext ?
The other thing is why do you need to call the fetchedResultsController to performFetch: again when you create a new Loan object? Is your data presented in a table view and have you implemented the NSFetchedResultsController delegate methods?
That call seems unnecessary and it may be causing issues with the cache created by the fetch. See section "Modifying the fetch request" under this link http://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40008227-CH1-SW24
Finally, try your delete operation directly in the view controller that received the action rather than pass it to the delegate (just to eliminate the possibility that something has been dealloc'd without you knowing).
Here's what I'd do:
- (IBAction)cancel:(id)sender
{
NSError *error;
NSManagedObjectContext *context = [self.loan managedObjectContext];
[context deleteObject:self.loan];
if (![context save:&error])
NSLog (#"Error saving context: %#", error);
}
I got a Bad Access because a deallocated UIViewController was a delegate of a NSFetchedResultsController it had.
The NSFetchedResultsController was deallocated - but when settings a delegate, it observes NSManagedObjectContext for changes, so when NSManagedObjectContext was saved - a bad access would occur when trying to notify the NSFetchedResultsController about the change.
Solution is to clear delegate of NSFetchedResultsController upon deallocation.
- (void)dealloc {
fetchedResultsController.delegate = nil;
}

How to load Core Data on another view besides the RootViewController?

I basically have the core data and the app working correctly except for the code in the AppDelegate. The code I'm having problems with is the following:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
RootViewController *tableController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
tableController.managedObjectContext = [self managedObjectContext];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tableController];
[tableController release];
[window addSubview: [self.navigationController view]];
[window makeKeyAndVisible];
}
I don't want to make the managedObjectContext the root view controller upon launch. I'm wanting to make it another view controller. However, if I change the classes to the view controller that I'm needing it for, it loads that view controller upon launch of the app, which is not what I want to do. I still want to launch the root view but I want to be able to load the core data context for my other view controller. I'm really confused on how to fix this issue. I've spent 2 days so far trying to find a way to fix this but no luck yet. Any help would be appreciated.
Also, if I leave out the following in the appdelegate didfinishlaunching:
RootViewController *tableController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
tableController.managedObjectContext = [self managedObjectContext];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tableController];
[tableController release];
I get this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'Hello'
EDIT:
Here is the entity code:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Lap Times";
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addTime:)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
[self fetchRecords];
}
- (void)addTime:(id)sender {
addTimeEvent *event = (addTimeEvent *)[NSEntityDescription insertNewObjectForEntityForName:#"addTime" inManagedObjectContext:self.managedObjectContext];
[event setTimeStamp: [NSDate date]];
NSError *error;
if (![managedObjectContext save:&error]) {
// This is a serious error saying the record could not be saved.
// Advise the user to restart the application
}
[eventArray insertObject:event atIndex:0];
[self.tableView reloadData];
}
- (void)fetchRecords {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"addTime" inManagedObjectContext:self.managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setEventArray: mutableFetchResults];
[mutableFetchResults release];
[request release];
}
Also if I use my own appdelegate called MyAppDelegate
MyAppDelegate *tableController = [[MyAppDelegate alloc] initWithStyle:UITableViewStylePlain];
tableController.managedObjectContext = [self managedObjectContext];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tableController];
I get the following error:
Object cannot be set- either readonly property or no setter found
I can't see the problem with the original approach you are taking? You are basically creating the managedObjectContext in your App delegate and passing it on to the tableController via assignment.
The alternative way to go about it is to get your viewController to "ask" for the managedObjectContext from the App delegate. So you'd still have your CoreData methods placed in your AppDelegate and use the following where you want to get a reference to the context. Because the managedObjectContext is lazily loaded on request, it will only get instantiated the first time you access the managedObjectContext method in your app delegate.
AppDelegate *theDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = theDelegate.managedObjectContext;
PS1: obviously you need to replace AppDelegate with the correct name for your App.
PS2: the reason you're getting the error when you make the changes is that there is no context available for CoreData to work with.
There is nothing magical about the RootViewController beside the name. You can rename it or you can exchange it with any other View Controller as long as these View Controller are configured right. You might want to take the RootViewController and adjust your new View Controller accordingly.
That said I don't understand what you want to do. You might want to post the code that doesn't work.
if you get an exception for +entityForName: you should post the code around +entityForName:.
If I had to make a guess I would say that your code looks like this:
entity = [NSEntityDescription entityForName:#"Hello"
inManagedObjectContext:managedObjectContext];
^^^^^^^^^^^^^^^^^^^^
this means you are using the managedObjectContext without the getter. And the getter uses lazy loading to load the context if it is needed for the first time.
I bet managedObjectContext is nil at this point. Use the debugger to check this out.
And then change the line like this:
entity = [NSEntityDescription entityForName:#"Hello"
inManagedObjectContext:self.managedObjectContext];
^^^^^^^^^^^^^^^^^^^^^^^^^
The code works when you include the four line about the rootviewcontroller because of this call:
tableController.managedObjectContext = [self managedObjectContext];
^^^^^^^^^^^^^^^^^^^^^^^^^^^
this will create the context if it is nil. Lazy loading.
[self managedObjectContext] is the same as self.managedObjectContext
but everything in my post is a guess because you didn't include the code around +entityForName:inManagedObjectContext: