in my iOS app i have a core data, and i have notice that sometime, in a specific view, when i retrieve information from core data, are not always up to date, i'm explain well:
if i update some value in the core data, and then i go in in a specific view to view this information, that information are not up to date, now i show how i access my database:
.h
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
.m
#synthesize managedObjectContext;
- (NSArray *)sortInformation{
if (managedObjectContext == nil) {
managedObjectContext = [(AppDelegate *) [[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"MyEntity" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
....
and then i display my information in a table view, all work perfectly, there is only this problem, that SOME TIME seems that the update i have done in another view is not read in this view, but if i close the app, and i close it from the background, and then i reopen it all works fine...so i have saved the update in the core data correctly, so i think the problem is in this view, maybe i have an old reference of the core data and not the update version, maybe the problem is this:
if (managedObjectContext == nil) {
managedObjectContext = [(AppDelegate *) [[UIApplication sharedApplication] delegate] managedObjectContext];
}
that refresh only if the variable managedObjectContext is nil so only if the view is deallocated...so never, because is one of my root view controller in a UITabbarController, so my question is, how i can access to the core data to have always a update version of it?
no need to refresh the context just call save method on managedObjectContext like [managedObjectContext save];
or if you are using more than one managed object context you should merge changes done by the context
On the implementation of the database class you can do like this
-(id) initWithContext: (NSManagedObjectContext *)managedObjContext {
self = [super init];
[self setManagedObjectContext:managedObjContext];
return self;
}
the managedObjContext is pass and set
On your app delegate when call the database class it should be something like this
database = [[Database alloc] initWithContext:self.managedObjectContext];
Then you are good to accessed the database like this
- (NSArray *)sortInformation {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSMutableArray *mutableFetchResults = [[[managedObjectContext_ executeFetchRequest:request error:&error] mutableCopy] autorelease];
[request release];
return mutableFetchResults;
}
Related
Im using a NSManagedObject as an attribute within my ViewController, declared like this:
#property(retain, nonatomic)NSManagedObject *person;
Im propagating the content of a fetch to a UITableView, when the user taps the contact he is looking for, this is what happens:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
self.person = [contacts objectAtIndex:indexPath.row];
Contacts contains the result of the fetch, which is done like this:
NSArray *contactsArray;
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [(OAuthStarterKitAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Contacts" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
testForTrue = [NSPredicate predicateWithFormat:#"SOME CONDITION"];
[fetchRequest setPredicate:testForTrue];
[fetchRequest setFetchLimit:10];
contactsArray = [[NSArray alloc]initWithArray:[context executeFetchRequest:fetchRequest error:&error]];
When the user taps the contact, self.person has that value, but when I try to use that value in another method it's nil, and the address is 0x000000.
This only happens on iOS 5, on iOS 6 person has the value of the contact selected and I can use elsewhere.
Any ideas?
Thanks.
Instead of getting the NSManagedObject, which is always dependant of the life cycle of the context I just stored de NSManagedObjectID and then get the NSManagedObject by using the function objectWithID. And it worked!
I'm using Xcode 4.2 and iOS SDK 5.0 and Apple's Master-Detail Application Template for iPad with Core Data. It's similar to the "Locations" sample code. I've successfully managed to pass the managed object context (MOC) from the MasterViewController (MVC) to the DetailViewController (DVC). The detail view controller accepts input from the user in some text boxes and stores that in core data; this part works like a charm. Now, I have a ActionViewController (AVC) that is a Popover View that is supposed to allow the user to e-mail all of the data in the MOC if they choose so. However, when trying to do a fetch I get a SIGABRT. I used breakpoints to pinpoint exactly where:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Fetch the crosses
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event"
inManagedObjectContext:context];
[fetchRequest setEntity:entity]; // Where it crashes
//Other code
}
Also, in the debugger I see that the *context pointer is 0x0 which I'm guessing means that it's not there?
The way I made the MVC pass the MOC was like this:
// Pass the managedObjectContext to the DetailViewController as a property
_detailViewController.managedObjectContext = self.managedObjectContext;
//Pass the MOC to the actionViewController
_actionViewController.managedObjectContext = self.managedObjectContext;
I've looked at other posts and it seems that passing the MOC to all the view controllers from the AppDelegate may be the way to go but I wanted to find out why the data input works for the DVC but crashes with fetching data in the AVC. What grand error have I made?
Fixed link to github project: https://github.com/scottdaniel/fly_punnett
So I figured it out, though I'm probably breaking some "good programming" maxim...
All I did was delete
//Pass the MOC to the actionViewController
_actionViewController.managedObjectContext = self.managedObjectContext;
along with all other connections from AVC to MVC (i.e. #class ActionViewController, #property *ActionViewController in the MCV interface) << ask me for the full details if you want them or check it out on github (link above)
Then I just added
#import AppDelegate.h
to my ActionViewController.m
and modified the MOC creation like so:
-(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Fetch the crosses
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication
sharedApplication] delegate]
managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Event"
inManagedObjectContext:context];
The MOC is created successfully and I'm able take the data for the user and put it into an e-mail they can send to whoever.
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;
}
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:
another iPhone newbie question...
I have the following:
NSPersistentStoreCoordinator
NSManagedObjectContext
NSManagedObjectModel
Is it possible to run queries directly on the store (since its a sqlite DB)? I'm trying to delete all the records from a tableview, and figured a "DELETE FROM table" would be nice and quick as opposed to looping through the records and removing them manually (which i'm also struggling with).
Thanks for your time,
James
Core data acts as a wrapper for the underlying data store, so it's not really a great idea to begin circumventing core data. Additionally, core data adds additional information to your DB, so directly accessing the DB may (or may in the future) cause problems.
To delete all records via core data, I have the following:
+ (void) deleteAll {
NSManagedObjectContext *managedObjectContext = [(myAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[[self class] description] inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
for (NSManagedObject *managedObject in items) {
[managedObjectContext deleteObject:managedObject];
NSLog(#"%# object deleted",[[self class] description]);
}
}