I have some really mysterious behaviour with CoreData.
I'll add an Object. I save this object. I fetch the new results and reload the collection view (from which is display the objects). The new object shows up. Hoorah! Just as expected.
I do this a second time, but every time from now (unless the app is restarted) when re-fetching the data from my NSFetchedResultsController and reloading the collection view, the new object doesn't appear.
Equally, if I delete an object. First time, A-OK! The next time I do this, the app actually crashes with the following error:
(Aircraft is my NSManagedObject)
Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd0000000000c0000 <x-coredata://C418948D-90CD-40E9-A502-C4CAB0134419/Aircraft/p3>''
*** First throw call stack:
(0x18b79f09c 0x197ad5d78 0x18b4a77ac 0x18b4a6cac 0x18b4a6b00 0x100034438 0x18e6d8a44 0x18e6d6dc0 0x18e6d2e44 0x18e66ed78 0x18e26b0cc 0x18e265c94 0x18e265b4c 0x18e2653d4 0x18e265178 0x18e25ea30 0x18b75f7e0 0x18b75ca68 0x18b75cdf4 0x18b69db38 0x19106f830 0x18e6dc0e8 0x1000217dc 0x1980bfaa0)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException
Time for some code. I can't see any issues, but here it is. I won't spam you with everything, but if something rings any alarms, I can always add it on request.
Starting with the main view controller. This contains my collection view. Just as a note, it has two sections each fetching data from an individual NSFetchedResultsController. I am only seeing the issue with this specific one though. Fairly standard fetched results controller.
- (NSFetchedResultsController *)aircraftFetchedResultsController
{
if (_aircraftFetchedResultsController != nil) {
return _aircraftFetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Aircraft" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:50];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.aircraftFetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.aircraftFetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _aircraftFetchedResultsController;
}
Anywhere I use an NSManagedObjectContext I am getting it from my AppDelegate. When adding the new object, the user is in a modal (form sheet) view controller. I create a new object, but do not insert it immediately, incase the user cancels:
SLAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Aircraft" inManagedObjectContext:managedObjectContext];
self.aircraft = [[Aircraft alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
Then, when done, save the object:
SLAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
//Only need to insert the new object if its 'NEW' else just save the existing one we are editing
if (!isEditing)
{
//Create new aircraft
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
//We are definetly saving the object, so now we insert it
[managedObjectContext insertObject:self.aircraft];
}
//Save
[appDelegate saveContextWithCompletionBlock:^(BOOL didSaveSuccessfully) {
if (didSaveSuccessfully)
{
[self dismissViewControllerAnimated:YES completion:^{
[delegate addAircraftDidSave:YES];
}];
}
else
{
[self dismissViewControllerAnimated:YES completion:^{
//ALERT with error
}];
}
}];
I use a delegate to send a message back to the main view controller saying the object has saved. That method then fetches the new data and reloads the collection view to show the new object:
-(void)fetchAircraft
{
NSError *error;
if (![[self aircraftFetchedResultsController] performFetch:&error])
{
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
[UIAlertView showGenericErrorAlert];
}
//Success, we have results
else
{
[self.collectionView reloadData];
}
}
Done. As I said, this works first time, then start acting up. Equally, you can substitute the save code for the delete code I have, fairly similar, delete and save changes:
SLAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
[managedObjectContext deleteObject:self.aircraft];
[appDelegate saveContextWithCompletionBlock:^(BOOL didSaveSuccessfully) {
if (didSaveSuccessfully)
{
[self dismissViewControllerAnimated:YES completion:^{
[delegate addAircraftDidSave:YES];
}];
}
else
{
//ALERT with error
}
}];
(From my above comment:) The two fetched results controllers must use different
caches (cacheName: parameter). I also think (but I am not 100% sure about that)
that without sections, a cache does not give any advantages, so you can also
try cacheName:nil.
I believe you'll need to use separate ManagedObjectContexts for saves on a background thread.
https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html
Related
I have a method which allows the user to export a .csv file of all data in my Core Data model. I'm using the wonderful CHCSV Parser after performing a fetchRequest to fetch the stored results. Once the .csv has been created, it gets attached to an email and exported from the device. This all works fine, the issues come when I delete the data.
I am using a somewhat popular technique to delete my data (by popular I mean it has been recommended on lots of Stack Overflow answers I have came researched). I simply fetch all objects and delete them one by one.
// Create fetchRequest
NGLSAppDelegate *appDelegate = (NGLSAppDelegate *)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NGLS"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// Delete objects
for (Model *results in fetchedObjects) {
[context deleteObject:(id)results];
NSLog(#"NGLS objects deleted");
};
My model is simple and only has two entities, so I use the same code for the other Admin entity too. This is all fine, all objects are deleted, and I can confirm this by doing another fetchRequest which returns the object count for each entity - both have a count of zero. The problem occurs when I try to save data back to either entity after the delete has been executed.
The ViewController with the above code is an "Admin" control screen, where the user can login and also export the data. So when the user logs in, here is the code to save:
// Save login details
[_managedObjectAdmin setValue:user forKey:#"userLogin"];
[_managedObjectAdmin setValue:site forKey:#"siteLocation"];
NSError *error;
[[self.managedObjectAdmin managedObjectContext] save:&error];
I then perform the fetchRequest above and export all data when the "Export" button is pressed. After that is complete, when I try to login on the same ViewController, the output is:
2014-10-27 12:43:49.653 NGLS[19471:607] <NSManagedObject: 0x7a8c2460> (entity: Admin; id: 0x7a82e3e0 <x-coredata://3C9F3807-E314-439C-8B73-3D4459F85156/Admin/p30> ; data: <fault>)
If I navigate back to another ViewController (using my navigation controller), then go back to the "Admin" control screen and try to login again, I get this output:
2014-10-27 12:44:04.530 NGLS[19471:607] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x7a82e3e0 <x-coredata://3C9F3807-E314-439C-8B73-3D4459F85156/Admin/p30>''
(I can post the full log if requested).
I have tried many things in an attempt to resolve this issue. Core Data is complex and I dont quite understand what I need to do to fix this. I have tried to delete the persistentStore and create it again, and the same with the managedObjectContext but nothing I have tried has worked. I implemented a resetStore method in my AppDelegate to delete and rebuild the store as follows:
- (void)resetStores {
NSError *error;
NSURL *storeURL = [[_managedObjectContext persistentStoreCoordinator] URLForPersistentStore:[[[_managedObjectContext persistentStoreCoordinator] persistentStores] lastObject]];
// lock the current context
[_managedObjectContext lock];
[_managedObjectContext reset];//to drop pending changes
//delete the store from the current managedObjectContext
if ([[_managedObjectContext persistentStoreCoordinator] removePersistentStore:[[[_managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error])
{
// remove the file containing the data
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
//recreate the store like in the appDelegate method
[[_managedObjectContext persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
}
[_managedObjectContext unlock];
}
And also tried this method:
- (NSPersistentStoreCoordinator *)resetPersistentStore
{
NSError *error = nil;
if ([_persistentStoreCoordinator persistentStores] == nil)
return [self persistentStoreCoordinator];
_managedObjectContext = nil;
NSPersistentStore *store = [[_persistentStoreCoordinator persistentStores] lastObject];
if (![_persistentStoreCoordinator removePersistentStore:store error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// Delete file
if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
// Delete the reference to non-existing store
_persistentStoreCoordinator = nil;
NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];
return r;
}
But neither of these work. What I have found to work is after the export has completed, by closing and reopening the app everything works as normal again. I have tried to figure out what process occurs when closing/opening the app in regards to Core Data but I just don't know what to look for. I have researched many questions on Stack Overflow and tried all the solutions but nothing works for me. I really need some help on this, or else I'm going to have to force the user to quit the app after the export is done by a exit(0); command (because it's not getting submitted to the App Store, its for in-house employees) although I don't want that solution, surely there is a way I can just reset my database and continue to use the app without having to shut it down every time. Thanks in advance.
References:
Deleting all records from Core Data
Reset a Core Data persistent store
How do I delete all objects from my persistent store in Core Data
I have managed to solve the issue by simply refreshing the view. By calling my viewDidLoad method after the export is complete I create a new managedObject ready to use as normal, without leaving that ViewController or exiting the app altogether.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib
// Create fetchRequest
NGLSAppDelegate *appDelegate = (NGLSAppDelegate *)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Admin" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// Fetch last object
Model *admin = [fetchedObjects lastObject];
// Populate login fields with last stored details
if (admin.userLogin == nil) {
//NSLog(#"Login empty");
} else {
self.usernameField.text = admin.userLogin;
self.siteLocationField.text = admin.siteLocation;
}
// Create new managed object using the Admin entity description
NSManagedObject *ManagedObjectAdmin;
ManagedObjectAdmin = [NSEntityDescription insertNewObjectForEntityForName:#"Admin"
inManagedObjectContext:context];
// Declare managed object
self.managedObjectAdmin = ManagedObjectAdmin;
// Save context
NSError *saveError = nil;
[context save:&saveError];
// ... more stuff
}
The solution was so simple all along. I will leave this question/answer up so other people can benefit from it if they find themselves in my situation. Thanks.
EDIT: Re-creating the managedObjectAdmin anywhere after the export works too, there is no specific need to call the viewDidLoad method. After the export has completed, I can simply create the new managedObject and save the context and continue using the app as normal, without having to navigate to another ViewController first or restarting the app.
I am only seeing the saved context after I restart my app. I have two view controllers, one that allows a user to save a core data record and the other to display. When I save a new record, then go to the table view to see all the records created, I do not see the new record. This is how I create a record:
NSManagedObjectContext *context = [self managedObjectContext];
Property *myProperty = [NSEntityDescription
insertNewObjectForEntityForName:#"Property"
inManagedObjectContext:context];
myProperty.aptDescription = curProperty.description;
NSError *error;
[context save:&error];
[appDelegate saveContext];
And then i retreive it doing this:
ATAppDelegate *appDelegate = (ATAppDelegate *)[[UIApplication sharedApplication]delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Property" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
self.myProperties = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
But like I said the new object is not shown when I go back into the table view immediately after creating the record. What is the issue?
Sounds like you are not implementing the delegate methods of the NSFetchedResultsController properly. Although you did not share any of your view code so it is impossible to be certain.
In addition, after a save you are not checking the BOOL return from the -save:. You should always, always, always check that BOOL and at least log the error. You could be throwing an error and never know it.
Problem in short
Since NSManagedObjectContext without persistent store coordinator doesn't support setFetchBatchSize selector, I've used a solution from this post and it works with certain issue, that I would like to resolve.
Here is the database scheme and Coredata structure with terms in brackets. Test application has two screens: master table with list of Chats and detail table with list of Messages. Master screen uses Main MOC in fetch controller for showing data in table and Worker MOC to create Chats and Messages. Detail screen uses Fetch MOC for showing data in table.
After I create a new Chat with Messages on master screen and save them with calling save on all MOCs in the hierarchy, I can't fetch Messages by a selected Chat in detail screen. All I got in console is: "CoreData: annotation: total fetch execution time: 0.0000s for 0 rows". it is possible to fetch this data after app restart.
It seems it has something to do with fault Messages in Fetch MOC having fault relations with Chats that have different objectID than Chats I have in Main MOC. Because when I fetch Chat object in Fetch MOC and then use it for finding Messages, everything is working fine.
I would appreciate it if someone could help me resolve this issue with Fetch MOC or maybe it is just fine to screw with all Object Graph concept and fetch data by my own ID fields instead of using relations.
Some code
Here is Coredata stack initialization which is done on didFinishLaunchingWithOptions:
- (void)initializeCoreDataStack
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"FaultsFetching" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];
_writerMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_writerMOC setUndoManager:nil];
[_writerMOC setPersistentStoreCoordinator:_persistentStoreCoordinator];
_mainThreadMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainThreadMOC setUndoManager:nil];
[_mainThreadMOC setParentContext:_writerMOC];
_fetchMainThreadMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_fetchMainThreadMOC setUndoManager:nil];
[_fetchMainThreadMOC setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[_fetchMainThreadMOC setPersistentStoreCoordinator:_persistentStoreCoordinator];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:_writerMOC];
NSURL *storeURL = [APP_DOC_DIR URLByAppendingPathComponent:#"FaultsFetching.sqlite"];
NSError *error = nil;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
- (void)backgroundContextDidSave:(NSNotification *)notification
{
[_fetchMainThreadMOC mergeChangesFromContextDidSaveNotification:notification];
NSLog(#"Yep, everything is merged");
}
Here is how I create Worker MOCs:
+ (NSManagedObjectContext *)createPrivateMOC
{
CoreDataManager *scope = [CoreDataManager sharedInstance];
NSManagedObjectContext *workerMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
workerMOC.parentContext = scope.mainThreadMOC;
[workerMOC setUndoManager:nil];
workerMOC.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
return workerMOC;
}
Here is how Multi-Context save looks like. Argument async is YES. Naturally this selector is called within performBlock selector of a worker MOC
+ (void)writeToDiskAsync:(BOOL)async
{
CoreDataManager *scope = [CoreDataManager sharedInstance];
NSManagedObjectContext *writeManagedObjectContext = scope.writerMOC;
NSManagedObjectContext *mainManagedObjectContext = scope.mainThreadMOC;
PerformBlock mainMOCBlock = ^
{
NSError *mainError = nil;
if ([mainManagedObjectContext hasChanges] && ![mainManagedObjectContext save:&mainError])
{
ALog(#"Unresolved error %#, %#", mainError, [mainError userInfo]);
}
PerformBlock writerBlock = ^
{
NSError *writeError = nil;
if ([writeManagedObjectContext hasChanges] && ![writeManagedObjectContext save:&writeError])
{
ALog(#"Unresolved error %#, %#", writeError, [writeError userInfo]);
}
NSLog(#"Yep, everything is saved");
};
[scope performBlock:writerBlock onMOC:writeManagedObjectContext async:async];
};
[scope performBlock:mainMOCBlock onMOC:mainManagedObjectContext async:async];
}
- (void)performBlock:(PerformBlock)block onMOC:(NSManagedObjectContext *)target async:(BOOL)async
{
if (async)
[target performBlock:block];
else
[target performBlockAndWait:block];
}
Here is my fetch results controller on detail screen, where "detailItem" is a Chat entity set from master screen and "[CoreDataManager sharedInstance]" is a singleton:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
if (self.detailItem == nil)
return nil;
NSManagedObjectContext *fetchMOC = [CoreDataManager sharedInstance].fetchMainThreadMOC;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Messages" inManagedObjectContext:fetchMOC];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sentDate" ascending:NO];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
NSPredicate *chatPredicate = [NSPredicate predicateWithFormat:#"relatedChat=%#", self.detailItem.objectID];
[fetchRequest setPredicate:chatPredicate];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:fetchMOC sectionNameKeyPath:#"sectionIdentifier" cacheName:nil];
_fetchedResultsController.delegate = self;
NSError *error = nil;
if (![_fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
A bit of background
Parent/child MOCs were used to improve stability and responsiveness in the app that wasn't properly written from the beginning. However, because now everything related to Coredata is more or less centralized, it is possible to change the stack to something different.
SectionIdentifier is used for grouping messages by day like this: http://i.imgur.com/17tuKS7.png
Something else I might add later, also sorry for links and images: reputation and silly stuff
This is due to a bug. The workaround is to call obtainPermanentIDsForObjects: before saving the newly inserted objects.
See the following SO issue for more details:
Core Data: Do child contexts ever get permanent objectIDs for newly inserted objects?
So I have a utility app and I am trying to save some text into a "To" and "Message: text field on the Flipside View Controller. However, my data won't save. I am new to objective C and I have been using multiple different tutorials to the point where I have totally confused myself. Hopefully you can help me out. Not sure what else to do at this point...
FlipsideViewController.m
#import "CCCFlipsideViewController.h"
#import "CCCAppDelegate.h"
#import "CCCMainViewController.h"
#import "MessageDetails.h"
#interface CCCFlipsideViewController ()
{
// NSManagedObjectContext *context;
}
#end
#implementation CCCFlipsideViewController
#synthesize allMessageDetails;
#synthesize managedObjectContext;
- (void)awakeFromNib
{
[super awakeFromNib];
CCCAppDelegate *appDelegateController = [[CCCAppDelegate alloc]init];
self.managedObjectContext = appDelegateController.managedObjectContext;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"MessageDetails" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
self.allMessageDetails = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
/*
NSManagedObject *managedObject; = [_fetchedResultsController valueForKey:#"to"];
self.toTextField.text = managedObject to;
messageDetails.to = [allMessageDetails firstObject];
self.toTextField.text = messageDetails.to;
messageDetails.message = [allMessageDetails valueForKey:#"message"];
self.messageTextField.text = messageDetails.message;
*/
NSLog(#"The 'to' is currently at %# after viewdidload", self.toTextField.text);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
return [textField resignFirstResponder]; //function says that if (bool) the text field is open and the keyboard hits return, text field is to resign first responder.
}
#pragma mark - Actions
- (IBAction)done:(id)sender
{
[self.delegate flipsideViewControllerDidFinish:self];
}
- (IBAction)resignFirstResponder:(id)sender {
[self.toTextField resignFirstResponder];
[self.messageTextField resignFirstResponder];
NSLog(#"Resigned First Responder");
}
- (IBAction)save:(id)sender {
// 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];
// If appropriate, configure the new managed object.
[newManagedObject setValue:self.toTextField.text forKey:#"to"];
[newManagedObject setValue:self.messageTextField.text forKey:#"message"];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
#pragma mark -
#pragma mark Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MessageDetails" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"to" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![_fetchedResultsController performFetch:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
#end
I didn't look at all your code because there was a problem near the top that negates everything you do thereafter. Don't alloc/init your app delegate in awakeFromNib or anywhere else for that matter. The one and only instance of your app delegate already exists (I have no idea what happens when there is more than one app delegate).
CCCFlipsideViewController needs to gain access to the managed object context through another means. Perhaps CCCMainViewController (or another view controller) could set the CCCFlipsideViewController's managedObjectContext property. If CCCMainViewController does not have access to the managed object context, have the app delegate pass that context to it.
Example:
App delegate sets a managedObjectContext property on the root view controller; the root view controller, in turn, sets the managedObjectContext property on a child view controller (like your flipside VC), etc.
You don't seem to ever actually set self.messageTextField.text or self.toTextField.text to anything -- you have commented out code in your viewDidLoad method which sets these fields. bilobatum is entirely correct about your AppDelegate issue as well -- you could also use something like
[((NSObject*)[UIApplication sharedApplication].delegate) valueForKey: #"managedObjectContext"];
to get the app delegate for your application if you want to fix that fast, though long term bilobatum's solution to this is better design.
Honestly, I think you've done quite a number on this code... ;)
OK, first off, in your save method, don't create another NSManagedObjectContext, use the instance variable you already declared, "managedObjectContext."
Secondly, I think you've made things way too complicated for yourself... Storing Core Data is actually shockingly simple once you've created the NSManagedObject subclasses and set up everything in the App Delegate...
It seems as if you wouldn't need any info from the "fetchedResultsController" at that point in your code, since you're saving, not fetching. Maybe try changing your save method to something like:
- (IBAction)save:(id)sender {
NSEntityDescription *entity = [NSEntityDescription insertNewObjectForEntityForName:#"MessageDetails" inManagedObjectContext:self.managedObjectContext];
// If appropriate, configure the new managed object.
[entity setValue:self.toTextField.text forKey:#"to"];
[entity setValue:self.messageTextField.text forKey:#"message"];
// Save the context.
NSError *error = nil;
[self.managedObjectContext save:&error]
if (error) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Edit: And to get the managed object context from the app delegate...
Right after your #synthesize's, create a variable for the App Delegate.
AppDelegate* appDelegateController;
And in viewDidLoad, initialize it:
appDelegateController = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Right after viewDidLoad (or anywhere you want), you can stick in a method to declare the managed object context:
- (NSManagedObjectContext*)managedObjectContext {
return appDelegateController.managedObjectContext;
}
Then back in viewDidLoad, call that method with:
self.managedObjectContext = [self managedObjectContext];
I am using CoreData in my application and it is a one-to-many relationship, I have two entities called folders and files. So a folder can have many files. So my first view controller shows all folders and when clicked on each folder, I show the files present in it.
Now all the folders and files present in it are created by user dynamically and are not coming from any database or from web server.
Now I have one more view controller, which I open from app delegate, and here I want to show some files.
I know that first we should pass the managedObjectContext, which will be declared in app delegate
ViewController *View = [[ViewController alloc]initWithNibName: #"ViewController" bundle:nil]
View.managedObjectContext = self.managedObjectContext;
So if we just pass managedObjectContext, can we access the file entity objects, OR we have to pass file entity object also.
Now as you know, I have two entities, called Folder and File, now to access files stored in any folder, I just do this.
NSMutableArray *filesArray = [self.folder.file mutableCopy];
But now, I want to show the files in anyView I want, which can be opened after two three navigations or it is opened directly from appDelegate.m.
To be very clear , my question is how to do this? i.e how can I get filesArray from AppDelegate.
Based on Below Answer edited. I have created a fetchResultsController this way
- (NSFetchedResultsController *)fetchedResultsController {
if (m_fetchResultsController != nil) {
return m_fetchResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"File" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"alarm" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"alarm!= nil"]];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return m_fetchResultsController;
}
and in ViewDidLoad and I written this way
- (void)viewDidLoad
{
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
NSMutableArray *alarms = [[self.fetchResultsController fetchedObjects]mutableCopy];
NSLog(#"alarmsCount:%d",[alarms count]);
}
Regards
Ranjit.
If you share more details we could help you, but in the meantime...
If the master controller shows the folder and the details controller show the files, simply inject a reference of the selected folder to the detail controller.
For example, create a property in the detail controller like this (it is needed to be synthesized)
#property (nonatomic,strong) Folders* currentFolder;
When you create a detail controller, do the following
DetailController* detCtr = // alloc-init here
detCtr.currentFolder = // the folder you want to pass in
Some Notes
Use Camel Case notation.
Use NSFetchedResultsController for lazy loading data in association with tables, etc.
Rename entity Files and Folders in their singular form.
Edit
Ok, so if you want to access your app delegate from everywhere within the app, you can just call
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate someMethod];
Here, you can retrieve the folder you are interesting in with a public property declared in .h and synthesized in .m.
Now, this in not a so good approach to follow. AppDelegate class would remain as is.
Another, more elegant, approach is to create a singleton called SharedManager (or something else) that store the currentFolder that need to visualize.
Shared Manager will look like
//.h
#property (nonatomic,strong) Folder* currentFolder;
+ (SharedManager*)sharedInstance;
//.m
#synthesize currentFolder;
+ (SharedManager*)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[SharedManager alloc] init];
});
return sharedInstance;
}
That, after importing it, it will be used to set a folder as the current one
[[SharedManager sharedInstance] setCurrentFolder:theFolderYouWantToShare];
or to retrieve the current folder
Folder* theSharedFolder = [[SharedManager sharedInstance] currentFolder];
P.S. Do not abuse on Singletons. The singleton pattern I use requires iOS 4 and greater.
Edit 2
It was my fault. I did not understand the question.
If you need to retrieve all the files, you could just create a NSFetchRequest like the following:
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"File" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
where results will contain all the files in your store indipendently from any folder. You can use this array to display files in your table.
If you have a lot of files, I really suggest to take a look at NSFetchedResultsController class. It allows lazy loading together with UITableView.
You can find a good tutorial here: How To Use NSFetchedResultsController
Edit 3
How to go about reminders?
You should provide to your request (the one you use with NSFetchedResultsController) a predicate like the following
[request setPredicate:[NSPredicate predicateWithFormat:#"reminder != nil"]];
In this manner you retrieve the files where reminder is NOT nil. reminder is the property you are using in the model.
P.S. Check the code because I've written without Xcode support.