I have finally managed to get core data working and beginning to understand it. So far I have just been playing in a window based app with core data enabled, playing inside the app delegate files.
But how can I access my managedObjectContext from outside the app delegate, for example if I had a UIView subclass?
Try using
[[[UIApplication sharedApplication] delegate] managedObjectContext];
To get rid of warnings, cast the delegate as your actual AppDelegate; for example,
NSManagedObjectContext *context = [(YourAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
EDIT:
After you change up any data, you'll need to save it. Here's the method I use:
NSManagedObjectContext *moc = [self managedObjectContext];
NSError *error;
if (![moc save:&error]) {
NSLog(#"Couldn't save current data in current method.");
}
Change up the log statement as you see fit.
Related
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;
}
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 am wondering what the correct way is to create my own initializer of a class that is subclassing NSManagedObject.
Currently I am initializing like this:
-(id)initWithXML:(TBXMLElement *)videoXML
{
// Setup the environment for dealing with Core Data and managed objects
HenryHubAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityHubPieceVideo = [NSEntityDescription entityForName:#"HubPieceVideo"
inManagedObjectContext:context];
self = [[HubPieceVideo alloc] initWithEntity:entityHubPieceVideo insertIntoManagedObjectContext:context];
// do stuff and then save
NSError *error;
if(![context save:&error])
{
NSLog(#"HubPiece video save context error: %# %#", error, [error userInfo]);
}
}
Seems like some others also do it this way.
Just found that the NSManagedObject reference says:
If you instantiate a managed object
directly, you must call the designated
initializer
(initWithEntity:insertIntoManagedObjectContext:).
I'm getting this classic error :
The model used to open the store is incompatible with the one used to create the store
This is how it's implemented :
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObject *newShot = [NSEntityDescription insertNewObjectForEntityForName:#"shotName" inManagedObjectContext:context];
NSString *newName= #"test";
[newShot setName:newName];
And this is how it's designed :
No only I'm getting a crash with the message above, I'm also getting this warning :
'NSManagedObject' may not respond to '-setName:'
Obviously something is wrong somewhere, I think I'm using Strings on both side though .
Edit, I'm now using this after Eimantas's comment :
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObject *newShot = [NSEntityDescription insertNewObjectForEntityForName:#"shotName" inManagedObjectContext:context];
NSString *newName= #"test";
[newShot setValue:newName forKey:#"shotNumber"];
[context saveAction];
But I`m still getting :
'NSManagedObjectContext' may not respond to '-saveAction'
Use setValue:forKey:
UPDATE
NSManagedObjectContext has save method, not saveAction. So:
NSError *error = nil;
[context save:&error]
if (error) {
[NSApp presentError:error];
return;
}
insertNewObjectForEntityForName:#"shotName" must be insertNewObjectForEntityForName:#"Shots". Shots is the entity name. shotName is the name of an attribute of the entity Shots. Also, like with Objective-C class names, it's standard to use singular names for your entity objects. So, Shots should be be Shot (recommended, but not required).
Also, if you change around your AppName.xcdatamodel file & generate new NSManagedObject files, you may also get the error: The model used to open the store is incompatible with the one used to create the store upon app launch. It's because it's using the old persistent store file. I call it: AppName.sqlite, but you may have a different name for this file. Search in your project for something like:
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"AppName.sqlite"]];
Then, once you know the name, to find the file, do:
find ~/Library/Application\ Support/ -name AppName.sqlite
Then, remove the file, and build & run again.
i have this code that passes the core data context to one of the controllers.
it was working great for few days, until the debugger started to give me the "not A type release" error and crashing the app.
i have checked the app for leaks and i found leaks from the SappDelegate object. so i understand that i have to release it but it keeping crashing every time i do it.
any ideas
thanks
shani
SAppDelegate *hbad= [[SAppDelegate alloc] init];
NSManagedObjectContext *context = [hbad managedObjectContext];
[hbad release];
if (!context) {
NSLog(#"problem with mannaged");
}
self.managedObjectContext = context;
If SAppDelegate is your actual app delegate, that is not the correct way to get it. you should change your code to:
SAppDelegate *hbad= [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [hbad managedObjectContext];
if (!context) {
NSLog(#"problem with mannaged");
}
self.managedObjectContext = context;
Also, leaks will not cause crashes until MUCH later when your application runs out of memory and the system kills it.
I'm not entirely sure why you're creating a new SAppDelegate. You should only have one of these and it gets created for you on startup. Why do you need another SAppDelegate instance?
You should see Elfred's answer to get the app delegate instead of creating one.
However, there is one bug in the code you have posted . . .
You need to retain the context until you're done with it. Either :
SAppDelegate *hbad= [[SAppDelegate alloc] init];
NSManagedObjectContext *context = [[hbad managedObjectContext] retain];
[hbad release];
if (!context) {
NSLog(#"problem with mannaged");
}
self.managedObjectContext = context;
[context release];
or release hbad later on :
SAppDelegate *hbad= [[SAppDelegate alloc] init];
NSManagedObjectContext *context = [hbad managedObjectContext];
if (!context) {
NSLog(#"problem with mannaged");
}
self.managedObjectContext = context;
[hbad release];