how do you create a NSManagedObjectContext - iphone

In core data for the iPhone, I was getting all sorts of errors trying to save data to a NSManagedObjectContext.
I believe that my issues were all to do with me using a NSManagedObjectContext that was being used in multiple threads.
So I wanted to create a new NSManagedObjectContext and try that, but I cannot find the example code to simply create a new instance...
I know its simple, but I would really appreciate any help here.
Note, I have seen this article on the Apple docs: http://developer.apple.com/iphone/library/documentation/cocoa/conceptual/CoreDataUtilityTutorial/Articles/05_createStack.html
But this uses some code that I am not familiar with, like the XMLStore which is not supported on the iPhone, etc.

this is the code to create a new context:
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *managedObjectContext = nil;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
[managedObjectContext setUndoManager:nil];
}
return [managedObjectContext autorelease];
}
It's simply create a new instance of the context and set the store that you would like to use.
If you have multiple stores, you would go for something like that:
- (NSManagedObjectContext *)managedObjectContextForStore:(NSString *)store {
NSManagedObjectContext *managedObjectContext = nil;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinatorForStore:store];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
[managedObjectContext setUndoManager:nil];
}
return [managedObjectContext autorelease];
}
For more info, please have a look at Apple's Core Data Tutorial for iOS.
Cheers!

Related

Fetching using NSFetchedResultsController vs NSManagedObjectContext directly

I had faced a problem and could resolve it, but I am curious to find out what exactly was the reason.
I have a shared class which should give me leave / vacation information of an employee. I have a fetch request in my utility class which provides the information of leave to fetch the objects.
This part of the code is responsible for fetching leaves:
#define FETCH_DIRECTLY 1
-(NSArray*)targetHoursArrayOnDate:(NSDate*)inDate
{
#if FETCH_DIRECTLY
NSFetchRequest *targetHoursFR = [CSUtilities fetchRequestForVacationOrLeave];
CSAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSArray *arrayOfTargetHours = [[appDelegate managedObjectContext] executeFetchRequest:targetHoursFR
error:NULL];
#else
NSArray *arrayOfTargetHours = [self.targetHoursFRC fetchedObjects];
#endif
NSPredicate *checkDatePredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings)
{
BOOL dateExists = NO;
if ([evaluatedObject isKindOfClass:[CSTargetHours class]])
{
CSTargetHours *aTargetHour = (CSTargetHours*)evaluatedObject;
if ([aTargetHour.leaveDate isEqualToDate:inDate])
dateExists = YES;
}
return dateExists;
}];
NSArray *targetHoursOnQueriedDate = [arrayOfTargetHours filteredArrayUsingPredicate:checkDatePredicate];
if (0==[targetHoursOnQueriedDate count])
targetHoursOnQueriedDate = nil;
return targetHoursOnQueriedDate;
}
Please note, FETCH_DIRECTLY is the scenario where my doubts are. If I use the FRC to fetch leaves, it fails. Whereas if I use the App delegate's managedObjectContext directly, it fetches objects successfully! This is puzzling me.
The way am creating my FRC is here:
#synthesize targetHoursFRC = targetHoursFRC_;
-(NSFetchedResultsController*)targetHoursFRC
{
if (nil==targetHoursFRC_)
{
NSFetchRequest *targetHoursFR = [CSUtilities fetchRequestForVacationOrLeave];
CSAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
targetHoursFRC_ = [[NSFetchedResultsController alloc] initWithFetchRequest:targetHoursFR
managedObjectContext:appDelegate.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
}
return targetHoursFRC_;
}
- (id)init
{
self = [super init];
if (self) {
[self.targetHoursFRC performFetch:NULL];
}
return self;
}
The only thing I am missing out here is, am not setting delegate to the FRC. But I dont need it since I am not interested in listening to the changes. Rather the leaves / vacation information should be ready when it is asked from some other module.
Is the internals of coredata somehow designed to inform FRC about the changes only if it has any delegates? Failing which, no matter when we trigger the -fetchedObjects call on FRC, it would give some old set of results?
Thanks,
Raj
If you don't set a delegate for the FRC, and implement at least one of the FRC delegate functions (e.g. controllerDidChangeContent:), then the FRC runs in the "no tracking mode".
That means that fetchedObjects will always return the result set of the initial performFetch: operation.
(See "Overview" section in the NSFetchedResultsController documentation).

refresh reference ManagedObjectContext

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;
}

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;
}

iOS 5 Core Data freeze

I try to do the following simple thing:
NSArray * entities = [context executeFetchRequest:inFetchRequest error:&fetchError];
Nothing fancy. But this freezes in iOS 5, it works fine in iOS 4. I don't get exceptions, warnings or errors; my app just simply freezes.
Please help me out! I'm dying here! ;)
I don't know if you also use different Thread. If yes the issue comes from the fact that NSManagedObjects themselves are not thread-safe. Creating a ManagedContext on the main thread and using it on another thread freezes the thread.
Maybe this article can help you :
http://www.cimgf.com/2011/05/04/core-data-and-threads-without-the-headache/
Apple has a demo application for handling Coredata on several threads (usually main & background threads) : http://developer.apple.com/library/ios/#samplecode/TopSongs/Introduction/Intro.html
What I've done to solve this issue is :
In the application delegate : create the persistent store (one for all thread) and create the Coredata managed Context for the main thread,
In the background thread, create a new managed context (from same persistent store)
Notifications are used when saving, to let the mainContext know when background thread has finished (inserting rows or other).
There are several solutions, using a NSQueueOperation. For my case, I'm working with a while loop. Here is my code if it may help you. However, Apple documentation on concurrency and their Top Songs example application are good points to start.
in the application delegate :
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.cdw = [[CoreDataWrapper alloc] initWithPersistentStoreCoordinator:[self persistentStoreCoordinator] andDelegate:self];
remoteSync = [RemoteSync sharedInstance];
...
[self.window addSubview:navCtrl.view];
[viewController release];
[self.window makeKeyAndVisible];
return YES;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator == nil) {
NSURL *storeUrl = [NSURL fileURLWithPath:self.persistentStorePath];
NSLog(#"Core Data store path = \"%#\"", [storeUrl path]);
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
NSError *error = nil;
NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
NSAssert3(persistentStore != nil, #"Unhandled error adding persistent store in %s at line %d: %#", __FUNCTION__, __LINE__, [error localizedDescription]);
}
return persistentStoreCoordinator;
}
-(NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext == nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return managedObjectContext;
}
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator == nil) {
NSURL *storeUrl = [NSURL fileURLWithPath:self.persistentStorePath];
NSLog(#"Core Data store path = \"%#\"", [storeUrl path]);
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
NSError *error = nil;
NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
NSAssert3(persistentStore != nil, #"Unhandled error adding persistent store in %s at line %d: %#", __FUNCTION__, __LINE__, [error localizedDescription]);
}
return persistentStoreCoordinator;
}
-(NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext == nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return managedObjectContext;
}
-(NSString *)persistentStorePath {
if (persistentStorePath == nil) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths lastObject];
persistentStorePath = [[documentsDirectory stringByAppendingPathComponent:#"mgobase.sqlite"] retain];
}
return persistentStorePath;
}
-(void)importerDidSave:(NSNotification *)saveNotification {
if ([NSThread isMainThread]) {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
} else {
[self performSelectorOnMainThread:#selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
}
}
In the object running the background thread :
monitor = [[NSThread alloc] initWithTarget:self selector:#selector(keepMonitoring) object:nil];
-(void)keepMonitoring{
while(![[NSThread currentThread] isCancelled]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//creating the cdw here will create also a new managedContext on this particular thread
cdwBackground = [[CoreDataWrapper alloc] initWithPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator andDelegate:appDelegate];
...
}
}
Hope this help,
M.
Thanks for the hints given in this page on how to solve this freezing issue which appeared on upgrading from iOS4. It has been the most annoying problem I have found since I started programming on iOS.
I have found a quick solution for cases where there are just a few calls to the context from other threads.
I just use performSelectorOnMainThread:
[self performSelectorOnMainThread:#selector(stateChangeOnMainThread:) withObject: [NSDictionary dictionaryWithObjectsAndKeys:state, #"state", nil] waitUntilDone:YES];
To detect the places where the context is called from another thread you can put a breakpoint on the NSLog on the functions where you call the context as in the following piece of code and just use performSelectorOnMainThread on them.
if(![NSThread isMainThread]){
NSLog(#"Not the main thread...");
}
I hope that this may be helpful...
I had the same issue. If you run under the debugger and when the app "hangs" stop th app (use the "pause" button on the debugger. If you're at the executeFetchRequest line, then check the context variable. If it has a ivar _objectStoreLockCount and its greater than 1, then its waiting on a lock on the associated store.
Somewhere you're creating a race condition on your associated store.
This really sounds like trying to access a NSManagedObjectContext from a thread/queue other than the one that created it. As others suggested you need to look at your threading and make sure you are following Core Data's rules.
Executing fetch request must happen from the thread where context was created.
Remember it is not thread safe and trying to executeFetchRequest from another thread will cause unpredictable behavior.
In order to do this correctly, use
[context performBlock: ^{
NSArray * entities = [context executeFetchRequest:inFetchRequest error:&fetchError];
}];
This will executeFetchRequest in the same thread as context, which may or may not be the main thread.
In my case the app would freeze before 'executeFetchRequest' without any warning. The solution was to wrap all db operations in #synchronized(persistentStore).
Eg:
NSArray *objects;
#synchronized([self persistentStoreCoordinator]) {
objects = [moc executeFetchRequest:request error:&error];
}
Delete all object with fetchrequest doesn't work for me, the sqlite looks corrupted. the only way I found is
//Erase the persistent store from coordinator and also file manager.
NSPersistentStore *store = [self.persistentStoreCoordinator.persistentStores lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
[self.persistentStoreCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
//Make new persistent store for future saves (Taken From Above Answer)
if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// do something with the error
}

app crashes when releasing appDelegate instance

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];