Managedobject is becoming nil when i try to load its attributes in a view controller. My operation flow is as below. All my operations are happening in the main thread(thread1).
I save a new entity in MOC1.
Then am getting this newly created entity in MOC2 (I have subscribed for NSManagedObjectContextDidSaveNotification and changes done in MOC1 is merged to MOC2).
Then the retrieved Managed Object is set to singleton class's property.
After that I load a view controller.
Inside view controller when I try to get attributes of managedobject from singleton class, I get nil.
Here I create a managedobject from moc2
ManagedObjectClass* someClass = (ManagedObjectClass*)[NSEntityDescription insertNewObjectForEntityForName:#"ManagedObjectClass" inManagedObjectContext:moc2];
someClass.orderID = #"123";
NSError *error = nil;
moc2 save:&error];
// Below is my NSManagedObjectContextDidSaveNotification handler
- (void) mocDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *savedContext = [notification object];
// ignore change notifications for the main MOC
if ([CoreDataController sharedCoreDataController].moc2 == savedContext)
{
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[[CoreDataController sharedCoreDataController].moc1 mergeChangesFromContextDidSaveNotification:notification];
});
}
// Now get the created someClass using moc1
dispatch_async(dispatch_get_main_queue(),^{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ManagedObjectClass" inManagedObjectContext:moc1];
[fetchRequest setEntity:entity];
NSPredicate *matchedPredicate = [NSPredicate predicateWithFormat:#"orderID = 123"];
[fetchRequest setPredicate:matchedPredicate];
NSError *error = nil;
NSMutableArray *fetchedObjects = [[[moc1 executeFetchRequest:fetchRequest error:&error]mutableCopy]autorelease];
// Here am able to see the contents of currentOrder
[appcontroller sharedAppController].currentOrder = [fetchedObjects objectAtIndex:0]
});
// Now load view controller
DetailsViewController *aSelectedViewController = [[DetailsViewController alloc]initWithNibName:#"DetailsViewController" bundle:nil];
UINavigationController aNavigationController = [[[UINavigationController alloc] initWithRootViewController:aSelectedViewController]autorelease];
self.applicationWindow.rootViewController = navigationController;
// View controller's implementation
#implementation DetailsViewController
- (void)viewDidLoad
{
if([appcontroller sharedAppController].currentOrder == nil)
{
NSLog(#"currentOrder became nil");
}
}
#end
Related
I'm making a SpriteKit game and I need to save the player's score using Core Data. I have a property with int value that starts off as being set to "5" and increment it x amount of times. I save it then transition to a different scene and fetch it. It shows up un-incremented with the initial value of "5".
I'm new to Core Data so forgive me if this is a stupid question, but how can I get Core Data to take the incrementation in to account? Or Is information being lost when I reference the property and how can I prevent this?
self.score = 5;
self.score++
and then save by calling this method.
AppDelegate.m
-(void) createObject {
Score *scoreEntity = (Score *)[NSEntityDescription
insertNewObjectForEntityForName:#"Score"
inManagedObjectContext:self.managedObjectContext];
SpaceshipScene *spaceshipSceneReference = [[SpaceshipScene alloc] init];
id points = [NSNumber numberWithInteger: spaceshipSceneReference.score];
scoreEntity.points = points;
scoreEntity.playerName = #"Joe";
NSError *error = nil;
// Saves the managedObjectContext
if (! [[self managedObjectContext] save:&error] ) {
NSLog(#"An error! %#", error);
}
}
This is how I call it.
SpaceshipScene.m
AppDelegate *appDelegateReference = [[AppDelegate alloc] init];
[appDelegateReference createObject];
I then fetch it in another class/scene using this method
AppDelegate.m
-(void)fetchObject {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Score"inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Sort fetched data
NSSortDescriptor *sortByPoints = [[NSSortDescriptor alloc] initWithKey:#"points" ascending:NO];
// Put them in an array
NSArray *sortDescriptor = [[NSArray alloc] initWithObjects:sortByPoints, nil];
// Pass the array to the fetch request
[fetchRequest setSortDescriptors:sortDescriptor];
NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Problem %#", error);
}
for (Score *s in fetchedObjects) {
NSLog(#" %# %d",s.playerName, [s.points integerValue]);
}
}
This is how I call it in the final scene/class
AppDelegate *appDelegateReference = [[AppDelegate alloc] init];
[appDelegateReference fetchObject];
Hope this will work, after fetching your Score entity , simply assign new values in it don't use insert object for update any value.
[scoreEntity setPoints:[NSNumber numberWithInteger: spaceshipSceneReference.score]];
[scoreEntity setPlayerName:#"Joe"];
then Save the values:
NSError *error = nil;
// Saves the managedObjectContext
if (! [[self managedObjectContext] save:&error] ) {
NSLog(#"An error! %#", error);
}
i'm trying to pass the newManagedObject to another viewcontroller, so i can make a relationship between my Song and Playlist entity.
Here i'm saving my object:
-(void)saveTap:(id)sender{
newManagedObject = (Playlists*)[NSEntityDescription insertNewObjectForEntityForName:#"Playlists" inManagedObjectContext:_managedObjectContext];
[newManagedObject setValue:self.textfield.text forKey:#"playlistName"];
// Save the context.
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self fetchDevices];
}
fetchDevices method:
- (void)fetchDevices {
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Playlists"];
devices = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableViewData reloadData];
}
On select i pass the newManagedObject to my Singleton [rowNumber singleObj]
The problem is that it returns nil instead of the playlistName. How can implement indexpath.row, so it will return the selectedRow playlistName.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
newManagedObject = (Playlists*)[NSEntityDescription insertNewObjectForEntityForName:#"Playlists" inManagedObjectContext:_managedObjectContext];
optionsSingle = [rowNumber singleObj];
optionsSingle.selectedRowNow = newManagedObject;
NSLog(#"%#", optionsSingle.selectedRowNow);
SongsViewController *songsViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SongsViewController"];
[self.navigationController pushViewController:songsViewController animated:YES];
}
If devices is your Fetched Results Controller then you should be able to get the record that corresponds to the row that was clicked using:
[devices objectAtIndexPath:indexPath.row];
I currently have a problem deleting an object in a to many relationship.
My app have the following relationship:
Product <<- Cart
When the user pushes a "add to cart" button in my viewcontroller, the following code is setting the relations between the product object and the cart
+ (Cart *)addProductToCartWithProduct:(Product *)product inManagedObjectContext:(NSManagedObjectContext *)context {
Cart *cart = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Cart"];
NSError *error = nil;
NSArray *carts = [context executeFetchRequest:request error:&error];
if (!carts || ([carts count] > 1)) {
// handle error
} else if (![carts count]) {
cart = [NSEntityDescription insertNewObjectForEntityForName:#"Cart" inManagedObjectContext:context];
} else { // they already have a cart started
cart = [carts lastObject];
}
/*Get Object ID to safely pass NSMangedObject between threads (A background worker thread and the main thread). */
NSManagedObjectID *retID = [product objectID];
[cart addProductsObject:(Product *)[context objectWithID:retID]];
//Inverse relationship
[(Product *) [context objectWithID:retID] setInCart:cart];
return cart;
}
This then returns a cart object, which I pass to my cart viewcontroller, and fetch the products in that relationship like so:
// Fetch request for "Product":
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Product"];
// Fetch only products for the cart:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"inCart = %#", self.cart];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"navn" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_theManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
When I then try to delete an object from the relationship like so:
-(void)RemoveFromCart:(UIButton *)sender {
NSIndexPath *ip = [NSIndexPath indexPathForRow:sender.tag inSection:0];
Product *prod = (Product *)[self.fetchedResultsController objectAtIndexPath:ip];
prod.inCart = nil;
[_cart removeProductsObject:prod];
NSLog(#"Cart %# %#", _cart.products, prod);
[self saveCurrentContext:_theManagedObjectContext];
[self loadCart];
[_orderTable reloadData];
}
The product is removed visually (gone from the tableview/screen) because inCart is set to nil, but not technically... when I log the cart object, the product object is still in the relationship, so it seems like the [_cart removeProductsObject:prod]; is not working.
And it also doesn't work the other way around, when I try to add the same product to the cart, I just have deleted (from the cart), for some reason the inverse relationship "inCart" is not set, after I have set it to "nil", when the product object is removed.
Why is this happening? and how do I fix it? :).
EDIT:
Pictures showcasing inverse relationships in Core data model editor:
Pass Cart to other viewcontroller:
[[[DataManager sharedInstance] backgroundManagedObjectContext] performBlock:^{
UITabBarController *tabBarController = self.tabBarController;
for (UINavigationController *navController in tabBarController.viewControllers) {
for (UIViewController *vc in navController.viewControllers) {
if ([vc isMemberOfClass:NSClassFromString(#"CartViewController")]){
CartViewController *cartVC = (CartViewController *) vc;
cartVC.cart = [Cart addProductToCartWithProduct:prod inManagedObjectContext: [[DataManager sharedInstance] backgroundManagedObjectContext]];
[[DataManager sharedInstance] saveBackgroundContext];
[[DataManager sharedInstance] saveMasterContext];
NSLog(#" %#", cartVC.cart);
}
}
}
}];
Save Context
-(void)saveCurrentContext:(NSManagedObjectContext *)context {
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"NOT SAVED");
}
[[DataManager sharedInstance] saveBackgroundContext];
[[DataManager sharedInstance] saveMasterContext];
}
Okay, so I found a work around for this very weird problem.
Since the autogenerated accessor methods didn't work for some reason, I had to think of another way to delete the object from the relationship.
for (Product *prod in _cart.products) {
//To Very reliable to check for item by name attribute, but It works :)
if ([prod.name isEqualToString:product.name]) {
product = prod;
NSMutableSet *set = [NSMutableSet setWithSet:_cart.products];
[set removeObject:prod];
_cart.products = set;
}
}
I've been following this tutorial to a T, but when I run my app, it fails on launch every time with the following error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a
legal NSManagedObjectContext parameter searching for entity name
'ArticleInfo''
But I can't figure out what I'm doing wrong that would cause that. As a background for what I've done, I have the datamodel file with an entity called ArticleInfo with a bunch of attributes of different types (some transient, if that's of note). Following the advice of this article I subclassed NSManagedObject as ArticleInfo.
If it's worth noting, I made the preview, wordsInBody and progress transient in the datamodel (and I gave position a default value of 0 in the Data Model Inspector). So in the subclass I made custom getters as follows:
- (NSString *)preview {
[self willAccessValueForKey:#"preview"];
NSString *preview = [self primitiveValueForKey:#"preview"];
[self didAccessValueForKey:#"preview"];
if (self.body.length < 200) {
preview = self.body;
}
else {
preview = [self.body substringWithRange:NSMakeRange(0, 200)];
}
return preview;
}
- (NSNumber *)progress {
[self willAccessValueForKey:#"progress"];
NSNumber *progress = [self primitiveValueForKey:#"progress"];
[self didAccessValueForKey:#"progress"];
if (self.body.length == 0) {
progress = #100;
}
else {
progress = #(100 * [self.position intValue] / [self.wordsInBody intValue]);
}
return progress;
}
- (NSNumber *)wordsInBody {
[self willAccessValueForKey:#"wordsInBody"];
NSNumber *wordsInBody = [self primitiveValueForKey:#"wordsInBody"];
[self didAccessValueForKey:#"wordsInBody"];
if (!wordsInBody) {
__block int numberOfWordsInBody = 0;
NSRange range = {0, self.body.length};
[self.body enumerateSubstringsInRange:range options:NSStringEnumerationByWords usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
numberOfWordsInBody++;
}];
wordsInBody = #(numberOfWordsInBody);
}
return wordsInBody;
}
(Again, not sure if that's relevant.)
Now in my main viewcontroller class (the one that has the tableview I'm using NSFetchedResultsController for) I declared the property, and overrode its getter:
In .h:
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
In .m:
- (NSFetchedResultsController *)fetchedResultsController {
if (!_fetchedResultsController) {
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ArticleInfo" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:descriptor];
request.fetchBatchSize = 20;
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:#"Root"];
_fetchedResultsController = fetchedResultsController;
_fetchedResultsController.delegate = self;
}
return _fetchedResultsController;
}
But again, it keeps giving me that error on app launch. What exactly am I doing wrong that's causing that error?
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ArticleInfo" inManagedObjectContext:context]
Something here is nil. Log context and entity to see which one. Maybe your entity name in your model isn't "ArticleInfo" (typo?) or your app delegate has a problem creating the context.
I'm simply trying to save a ManagedObjectContext but while I get no errors, the fetched request returns the object with none of the saved values. Consider this simple example. You have a single object, you change a property and save it. The object is there but the property is not saved. As you can see, I want only one object, and the fetch returns this one object. BTW, the code is in a simple class, not the app delegate or a view controller.
Here is the sample code:
MyAppDelegate* delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext* context = delegate.managedObjectContext;
NSEntityDescription *myEntityDesc = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:myEntityDesc];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:request error:&error];
MyEntity* myEntity;
if (array == nil || [array count] < 1)
{
myEntity = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity" inManagedObjectContext:context];
}
else
{
myEntity = [array objectAtIndex:0];
}
myEntity.BoolValue = [NSNumber numberWithBool:someBoolValue];
myEntity.ID = #"Some ID";
if ([context save:&error])
{
NSLog(#"no error");
}
else
{
NSLog([NSString stringWithFormat:#"found core data error: %#", [error localizedDescription]]);
}
Here's the code used to retrieve the values later:
MyAppDelegate* delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext* context = delegate.managedObjectContext;
NSEntityDescription *MyEntityDesc = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:MyEntityDesc];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:request error:&error];
MyEntity* myEntity;
if (array == nil || [array count] < 1)
{
//handle error
}
else
{
myEntity = [array objectAtIndex:0];
}
return [myEntity.BoolValue boolValue];
What does your NSManagedObject subclass look like? Since the fetch is working correctly (i.e. returning the entity), I suspect something is wrong in the subclass implementation.
You should declare a #property for each of the attributes on your data model. And in the implementation file, instead of using #synthesize you need to use #dynamic. Also make sure in your xcdatamodel that the entity has its class set, as well as the name.
#interface MyEntity : NSManagedObject
#property (nonatomic, strong) NSNumber * boolValue;
#end
#implementation MyEntity
#dynamic boolValue;
#end