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
Related
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
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 new to NSManagedObjectContext. I have created an entity Link in my app, which contains a NSString *url.
At some point of my app, I need to insert a new Link in my base, so I simply do this :
Link *link = [NSEntityDescription
insertNewObjectForEntityForName:#"Link"
inManagedObjectContext:self.managedObjectContext];
link.url = myUrl;
But before doing this, I want to check if there is not already a Link in my base with the same url. And I have no idea of how to do so... what should I do?
EDIT : to retrieve data from the base I'm using this method from a tool I found on the web:
// Fetch objects with a predicate
+(NSMutableArray *)searchObjectsForEntity:(NSString*)entityName withPredicate:(NSPredicate *)predicate andSortKey:(NSString*)sortKey andSortAscending:(BOOL)sortAscending andContext:(NSManagedObjectContext *)managedObjectContext
{
// Create fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// If a predicate was specified then use it in the request
if (predicate != nil)
[request setPredicate:predicate];
// If a sort key was passed then use it in the request
if (sortKey != nil) {
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
}
// Execute the fetch request
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
// If the returned array was nil then there was an error
if (mutableFetchResults == nil)
NSLog(#"Couldn't get objects for entity %#", entityName);
// Return the results
return mutableFetchResults;
}
I would like to know how to use it.
Thanks for your help.
The method you provided just searches for a NSManagedObject that matches the attributes in the NSManagedObjectContext and if one exists, it returns it.
But, what you need to implement is called the Find-or-Create pattern, which is discussed in the Core Data programming guide. Basically, you search for an object matching specific criteria and if it exists that object is returned. If that object does not exist you create it.
Core Data Programming Guide
E.g.
+ (NSString *)entityName
{
return NSStringFromClass([Link class]);
}
+ (instancetype)findUsingPredicate:(NSPredicate *)predicate withContext:(NSManagedObjectContext *)context
{
Link * entity;
// Setup the fetchRequest
NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[[self class] entityName]];
fetchRequest.predicate = predicate;
// Credit: #Martin R
[fetchRequest setFetchLimit:1];
// Execute the fetchRequest
NSError *error = nil;
NSArray * matchingLinks = [context executeFetchRequest:fetchRequest error:&error];
// MatchingLinks will only return nil if an error has occurred otherwise it will return 0
if (!matchingLinks)
{
// handle error
// Core data returns nil if an error occured
NSLog(#"%s %#", __PRETTY_FUNCTION__, [error description]);
}
else if ([matchingLinks count] <= 0)
{
// if the count <= 0, there were no matches
NSLog(#"%s Not found", __PRETTY_FUNCTION__);
} else {
// A link with a url that matches the url in the dictionary was found.
NSLog(#"%s Found", __PRETTY_FUNCTION__);
entity = [matchingLinks lastObject];
}
return entity;
}
+ (instancetype)findUsingPredicate:(NSPredicate *)predicate orCreateWithContext:(NSManagedObjectContext *)context
{
Link * entity = [[self class] findUsingPredicate:predicate withContext:context];
if (!entity) {
entity = [[self class] createWithContext:context];
}
return entity;
}
+ (isntancetype)createWithContext:(NSManagedObjectContext *)context
{
return [[self class] alloc] initWithContext:context];
}
- (instancetype)initWithContext:(NSManagedObjectContext *)context
{
Link * entity = [NSEntityDescription insertNewObjectForEntityForName:[[self class] entityName] inManagedObjectContext:context];
return entity;
}
USE CASE:
NSString * url = #"http://www.mylink.com";
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"url = %#", url];
Link * link = [Link findUsingPredicate:predicate orCreateWithContext:self.managedObjectContext];
link.url = url;
You can do it like this (with your method):
AppDelegate *del = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = del.managedObjectContext;
NSString *urlString = #"YOUR URL HERE";
NSMutableArray *fetchedResults = [self searchObjectsForEntity:#"Link" withPredicate:[NSPredicate predicateWithFormat:#"url == %#", urlString] andSortKey:#"url" andSortAscending:YES andContext:managedObjectContext];
BOOL exist = NO;
if(fetchedResults.count >= 1){
exist = YES;
}
I am trying to work with core data and already am able to store records of my class "Event.m". An event has a timestamp an a note.
But as i try to read all entries in my table view to display them, I see that every timestamp and note is nil (debug). Strange thing is, that my array I get has the right size (number of entries) and also the table rows are created (only no note si visible).
Here some of my code:
- (void)fetchRecords {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
NSLog(#"Error fetching data.");
}
// Save our fetched data to an array
[self setEventArray: mutableFetchResults];
NSLog(#"Records found: %i", [eventArray count]);
}
And this is where I save my object:
- (void) saveEntry: (NSString *) note
{
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:managedObjectContext];
[event setTimeStamp: [NSDate date]];
[event setNote:#"test"];
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"Could not save entry.");
}
NSLog(#"Entry saved.");
[eventArray insertObject:event atIndex:0];
}
Note that there are two different views, from which I access Core Data.
And this is where I get my MOC in this views:
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
managedObjectContext = appDelegate.managedObjectContext;
}
Here I was reading, that it could come frome the MOC itself. This is how I hold my variable in SecondViewController.h
#import "Event.h"
#import <CoreData/CoreData.h>
#import "AppDelegate.h"
#interface SecondViewController : UITableViewController {
NSManagedObjectContext *managedObjectContext;
NSMutableArray *eventArray;
}
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) NSMutableArray *eventArray;
- (void) fetchRecords;
#end
And in my .m file I have:
#synthesize managedObjectContext, eventArray;
Then I call fetchRecords.
Any ideas why I have this problem?
I was missing one line of code, concerning the request:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setReturnsObjectsAsFaults:NO];
After adding the last line I was able to access all attributes.
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.