I'm setting up a very simple NSIncrementalStore example using AFIncrementalStore.
The idea is to setup a NSManagedObjectContext in the AppDelegate (using the ordinary template provided by Apple, with changes for my IncrementalStore), do a fetch with no predicate or sort descriptor and NSLog the one fetched entity object.
Everything works great until I ask for any entity attribute. It crashes with the following message:
2013-07-22 16:34:46.544 AgendaWithAFIncrementalStore[82315:c07] -[_NSObjectID_id_0 eventoId]: unrecognized selector sent to instance 0x838b060
2013-07-22 16:34:46.545 AgendaWithAFIncrementalStore[82315:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_NSObjectID_id_0 eventoId]: unrecognized selector sent to instance 0x838b060'
My xcdatamodeld is correctly setted up. The NSManagedObject class is generated and imported on the delegate. When I do a breakpoint before the NSLog I can see the fetched objects IDs. The webservice is giving me back the correct data.
My AppDelegate code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(remoteFetchHappened:) name:AFIncrementalStoreContextDidFetchRemoteValues object:self.managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Agenda" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = entityDescription;
fetchRequest.predicate = nil;
NSError *error;
[self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return YES;
}
// Handle the notification posted when the webservice returns objects
- (void)remoteFetchHappened:(NSNotification *)aNotification
{
NSArray *fetchResult = [[aNotification userInfo] objectForKey:#"AFIncrementalStoreFetchedObjectIDs"];
Agenda *agenda = (Agenda *)[fetchResult lastObject];
// THIS IS WHERE IT BREAKS...
NSLog(#"Agenda: %#", agenda.eventoId);
}
Any ideas on how to make this piece of code return the attribute I'm asking for?
AFNetworking is giving you managed object IDs, that is, instances of NSManagedObjectID. You can't look up managed object property values on that-- you have to get the managed object for the ID first. That's what _NSObjectID_id_0 means in the error message-- you're trying to get
eventoId on an NSManagedObjectID, and it has no idea what that is.
You get the managed object by looking it up in the managed object context. Something like
NSError *error = nil;
NSManagedObject *myObject = [context existingObjectWithID:objectID error:error];
if (myObject != nil) {
// look up attribute values on myObject
}
Related
I have a class with a method like this:
- (void)getEntities
{
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = appDelegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"MyEntity"];
NSArray *entities = [mainContext executeFetchRequest:fetchRequest error:nil];
for (NSManagedObject *item in entities) {
NSLog(#"Name: %#", ((MyEntity *)item).name);
}
}
I have enabled the com.apple.CoreData.ConcurrencyDebug 1 and when I call this method (in main thread) I get the error:
CoreData: error: The current thread is not the recognized owner of this NSManagedObjectContext(0x16d40160). Illegal access during executeFetchRequest:error:
However, if I do:
- (void)getEntities
{
NSManagedObjectContext *privateContext = [CoreDataStack getPrivateContext];
if (privateContext != nil) {
[privateContext performBlock: ^{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"MyEntity"];
NSArray *entities = [privateContext executeFetchRequest:fetchRequest error:nil];
for (NSManagedObject *item in entities) {
NSLog(#"Name: %#", ((MyEntity *)item).name);
}
}];
}
}
And I call it also in main thread, it does not seem to cause such Core Data error... why? I've been always assuming that the NSManagedObjectContext provided by default in AppDelegate and I'm retrieving belongs to main thread... What I'm doing wrong or missing?
The entities I'm trying to fetch using the context from AppDelegate where previously saved from a context that was created in a private queue (similar to the one I create in my second code snippet). Could that be the cause of the error? Such private context was not a child of any other context, and I had no error when saving it.
I need to get the entities I have already stored by using a private context in the main thread, to keep them in an NSArray in the implementation of an UIViewController.
Thanks so much in advance.
It seems that the problem wasn't in the method but my initialization of the context in AppDelegate: before calling the method in the question, and in certain scenario I was retrieving the context the first time from a queue that wasn't the main thread, and that seemed to be causing the context to be initialized in that other queue.
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
In my app I have 2 detail view controllers:
product name update controller
product properties update controller
In product properties update view controller everything is fine there is nothing wrong.
In product name update view controller however saving the context does not give any error. I see that the product name change in root view controller but when I re-open my app the product name shows me the old name. So it is not persisted.
What is my problem according to my updateProduct method:
-(void)updateProduct:(id)sender
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Products" inManagedObjectContext:context]];
NSError *error = nil;
NSPredicate *predicateID =[NSPredicate predicateWithFormat:#"productID==%d",[secim intValue]];
[request setPredicate:predicateID];
NSArray *myobj=[context executeFetchRequest:request error:&error];
NSManagedObject *prod1=[myobj objectAtIndex:0];
[prod1 setValue:textProduct1.text forKey:#"productName"];
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self.navigationController popToRootViewControllerAnimated:YES];
NSLog(#"Data saved");
}
In addition to #Mundi here you are doing the following
NSArray *myobj = [context executeFetchRequest:request error:&error];
NSManagedObject *prod1 = [myobj objectAtIndex:0];
but you are not checking against error and neither if myobj contains elements.
ALWAYS check for errors whereas there is the possibility to have them.
Check with a breakpoint if your updateProduct method is called. I suspect it is not.
In the light of the other discussion, make sure that
the managed object context is not nil
prod1 (the managed object) is not nil
You can do this with breakpoints in the debugger or with log statements.
I'd like to delete a core data object by fetched the object first, so
in FetchObject.m
- (void) actionDelete {
AModel *aModel = [[aModel alloc] init];
AObj *aObj = [aModel readDataWithAttributeName:#"keyword" attributeValue:#"value"];
[aModel deleteObject:aObj];
}
aObj did fetch and obtain.
in AModel.m
- (void)deleteObject:(AObj *)aObj
{
[appDelegate.managedObjectContext delete:aObj];
NSError *error;
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Error: %#", [error description]);
}
}
But, when I test it, here came out an error
-[NSManagedObjectContext delete:]: unrecognized selector sent to instance 0xa43ece0
After searching the solution a bit, seems like the target has been release before deleteObject.
Is there any way to solve the problem?
The following code is causing the issue:
[appDelegate.managedObjectContext delete:aObj];
Replace it with:
[appDelegate.managedObjectContext deleteObject:aObj];
NSManagedObjectContext doesn't have a delete method, it only has a deleteObject method.
- (void)deleteObject:(NSManagedObject *)object
Parameters
object
A managed object.
Discussion
When changes are committed, object will be removed from the uniquing
tables. If object has not yet been saved to a persistent store, it is
simply removed from the receiver.
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];