CoreData issue with appDelegate - objective-c

I'm relatively new to Objective C. So far everything has been going really well until I hit CoreData. I just can't get it to work! After spending many hours on something that seems to be pretty straightforward, I'm at my wits' end.
PLEASE help me figure out what I have done wrong:
I created a new Windows-Based app and checked 'use Core Data for storage'
In the xcdatamodel, I created an entity named 'RecipeData' with only one attribute 'recipeName' it is a string
in the app delegate, I load an XML file and parse it. When I parse the recipe name, I use the following:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:managedObjectContext];
I get the following error:
terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'RecipeData'
Which leads me to the big 3 questions:
is there anything really obvious that I am doing wrong?
since I checked 'use Core Data for storage,' it seems the following code is injected automatically into the app delegate .h:
#private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
Does this interfere with the code I am using?
I tried creating a new NSManagedObjectContext called *myManagedObjectContext but that did not work.
One other tidbit, when I add the following right above my code:
if (managedObjectContext == nil) {
NSLog(#"NO CONTEXT");
}
The console prints "NO CONTEXT"
I really appreciate any help. Thanks.

Where has managedObjectContext come from? Is it a typo for managedObjectContext_? The project templates create the latter, not the former. Using the code above with the code provided by the standard project templates should produce a syntax error. I'm guessing you've renamed some things?
You seem to be using managedObjectContext as an ivar. It is a property. Inside the class, there is a private managedObjectContext_ ivar which holds the reference to the object context. You shouldn't access this. You should be accessing the managedObjectContext property. When this property is first accessed, its getter method will create the context for you. Since you aren't accessing the property, the getter method isn't called and the context never gets created.
Where you have code like this:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:managedObjectContext];
...you should be using code like this:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:self.managedObjectContext];
Note the self. bit. This means that you are accessing a property on the self object, not accessing an ivar from the object the method is being called on.
Note that reading a property is the same as calling the getter method, so the above can also be written as:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:[self managedObjectContext]];

Related

Objective C: Request for member XXX in something not a structure or union. (Core Data)

I am hitting this error when implementing core data.
I have created a entity 'FlashCard' with the attribute 'question' and 'answer'. Both the attributes are of NSString type.
After inserting a new object into the NSManaged Object, I tried to set the 2 attributes as seen below.
NSManagedObject *newCard = [NSEntityDescription insertNewObjectForEntityForName:#"FlashCard" inManagedObjectContext:self.managedObjectContext];
newCard.question = thisQuestion;
newCard.answer = thisAnswer;
But when I try to compile the code, I am hitting the error "Request for member 'question' in something is not a structure or union'. I get the same error for newCard.answer line.
Any advise on how to resolve this is greatly appreciated!
Zhen
Your newCard instance should be of type FlashCard not NSManagedObject; otherwise, the compiler won't know that newCard has the properties question and answer.
FlashCard *newCard = (FlashCard *)[NSEntityDescription insertNewObjectForEntityForName:#"FlashCard" inManagedObjectContext:self.managedObjectContext];
newCard.question = thisQuestion;
newCard.answer = thisAnswer;
You've declared newCard as an NSManagedObject, then tried to access properties that NSManagedObject doesn't define.
Core Data gives you the option of using a custom subclass of NSManagedObject to represent entities. If you're doing this then, as others have suggested, you need to declare newCard as an instance of that subclass (in case you're not doing this, you'll have to write the class and declare the properties yourself if you want to use the 'dot' property syntax --- core data doesn't automatically create a subclass of NSManagedObject for each entity type)
Also, you don't have to use your own subclass or write accessors just to access a managed object's attributes and relationships. If you don't need to add any custom logic to FlashCard yet, you can use key value coding on an NSManagedObject instead. This would work fine:
NSManagedObject *newCard = [NSEntityDescription insertNewObjectForEntityForName:#"FlashCard" inManagedObjectContext:self.managedObjectContext];
[newCard setValue: thisQuestion forKey: #"question"];
[newCard setValue: thisAnswer forKey: #"answer"];
#import "FlashCard.h"
Is "FlashCard.h" included at the top of this file?

can't find managed object model?

I created a entity called photo inmy .xcdatamodel.
but when I tried to add it into my context:
NSManagedObjectContext *context = [self managedObjectContext];
Photo *p = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:context];
it had run-time error:
+entityForName: could not locate an NSManagedObjectModel for entity name
'Photo'
it's really weird, I included the Photo.h, generated by xcode coredata.
does anyone have an idea why it goes wrong?
I can't find what's wrong at all.. > <
Thanks!
Make sure your call to [self managedObjectContext] is returning a valid context (and not nil). Also make sure you're using the proper case (you said your entity was called "photo" but you're trying to insert a new object for an entity named "Photo").

Crash in OS X Core Data Utility Tutorial

I'm trying to follow Apple's Core Data utility Tutorial. It was all going nicely, until...
The tutorial uses a custom sub-class of NSManagedObject, called 'Run'. Run.h looks like this:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Run : NSManagedObject {
NSInteger processID;
}
#property (retain) NSDate *date;
#property (retain) NSDate *primitiveDate;
#property NSInteger processID;
#end
Now, in Run.m we have an accessor method for the processID variable:
- (void)setProcessID:(int)newProcessID {
[self willChangeValueForKey:#"processID"];
processID = newProcessID;
[self didChangeValueForKey:#"processID"];
}
In main.m, we use functions to set up a managed object model and context, instantiate an entity called run, and add it to the context. We then get the current NSprocessInfo, in preparation for setting the processID of the run object.
NSManagedObjectContext *moc = managedObjectContext();
NSEntityDescription *runEntity = [[mom entitiesByName] objectForKey:#"Run"];
Run *run = [[Run alloc] initWithEntity:runEntity insertIntoManagedObjectContext:moc];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
Next, we try to call the accessor method defined in Run.m to set the value of processID:
[run setProcessID:[processInfo processIdentifier]];
And that's where it's crashing. The object run seems to exist (I can see it in the debugger), so I don't think I'm messaging nil; on the other hand, it doesn't look like the setProcessID: message is actually being received. I'm obviously still learning this stuff (that's what tutorials are for, right?), and I'm probably doing something really stupid. However, any help or suggestions would be gratefully received!
===MORE INFORMATION===
Following up on Jeremy's suggestions:
The processID attribute in the model is set up like this:
NSAttributeDescription *idAttribute = [[NSAttributeDescription alloc]init];
[idAttribute setName:#"processID"];
[idAttribute setAttributeType:NSInteger32AttributeType];
[idAttribute setOptional:NO];
[idAttribute setDefaultValue:[NSNumber numberWithInteger:-1]];
which seems a little odd; we are defining it as a scalar type, and then giving it an NSNumber object as its default value. In the associated class, Run, processID is defined as an NSInteger. Still, this should be OK - it's all copied directly from the tutorial.
It seems to me that the problem is probably in there somewhere. By the way, the getter method for processID is defined like this:
- (int)processID {
[self willAccessValueForKey:#"processID"];
NSInteger pid = processID;
[self didAccessValueForKey:#"processID"];
return pid;
}
and this method works fine; it accesses and unpacks the default int value of processID (-1).
Thanks for the help so far!
If you are getting EXC_BAD_ACCESS on
[run setProcessID:[processInfo processIdentifier]];
it's almost certainly due to one of the pointers no longer pointing to a real object. Either run has been dealloc'd or processInfo has been dealloc'd. This assumes that the line is not the next line of code after
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
If it is, then both objects should be valid, so you are probably looking at something wrong with
[self willChangeValueForKey:#"processID"];
or
[self didChangeValueForKey:#"processID"];
if you have any objects observing that key, it's possible they have gone stale somehow.
This did not solve my problem with willAccessValueForKey but it solved a mystery. I subclassed an entity but I forgot to set the custom class in the model. The custom class worked until I wrote a method to send back a string which was a concatenation of some of the properties. The method was nearly identical to another method that was in another custom NSManagedObject class. I could not figure out why the one class worked and the other didn't. Once I set the custom class in the model they both worked.

Customized initializers and read only properties in Core Data

Before working with Objective-C and Core Data, I had occasions to create classes that needed to be initialized with certain parameters that, after initialization, could not be modified (though they could be read).
With Core Data, I believe I can create a customized init on my NSManagedObject derived class as long as it includes a way to insert the object into a context like such:
-(Cell*) initWithOwner:(CellList*)ownerCellList andLocation:(int)initLocation
{
if (self = (Cell*) [NSEntityDescription insertNewObjectForEntityForName:#"Cell"
inManagedObjectContext:[ownerCellList managedObjectContext]])
{
self.location = [NSNumber numberWithInt:initLocation];
self.owner = ownerCellList;
[ownerCellList addCellListObject:self];
}
return self;
}
Normally, I'd have a location variable and the location property would be read-only (so once set at initialization, it could not be changed). Is there a way to get this sort of pattern with Core Data? Is there a better way I'm not thinking of?
Thanks!
You are correct. As long as your initializer calls the NSManagedObject's designated initializer, your approach is fine. You can also override the -[NSManagedObject awakeFromInsert] to perform some action after insertion (creation) or -[NSManagedObject awakeFromFetch] to perform an action (e.g. populating a cache) each time the object is faulted back into a managed object context.
Like the rest of Objective-C, there is no way to make a property truly readonly. Malicious code will likely be able to modify your property. However, in your custom class, you can declare a #property(readonly) for e.g. location. This will at least cause a warning if you try to modify the property and will signal your intent to client code.
For anyone who stumbles here, reads the comments, and wonders at the final answer, it should be something like this. Continuing with the example above, it would be:
-(Cell*) initWithOwner:(CellList*)ownerCellList andLocation:(int)initLocation
{
NSManagedObjectContext *context = [ownerCellList managedObjectContext];
NSManagedObjectModel *managedObjectModel =
[[context persistentStoreCoordinator] managedObjectModel];
NSEntityDescription *entity =
[[managedObjectModel entitiesByName] objectForKey:#"Cell"];
self = [self initWithEntity:entity inManagedObjectContext:context];
if (self)
{
self.location = [NSNumber numberWithInt:initLocation];
self.owner = ownerCellList;
[ownerCellList addCellListObject:self];
}
return self;
}
NSEntityDescription's insertNewObjectForEntityForName:inManagedObjectContext: documentation says this is roughly how it converts from a given entityName (#"Cell") and a context (from ownerCellList) to an NSManagedObject instance.

What is the type for boolean attributes in Core Data entities?

I am using Core Data programmatically (i.e. not using .xcdatamodel data model files) in much the same manner as depicted in Apple's Core Data Utility Tutorial. So my problem is that when I try to add an attribute to an entity with the type NSBooleanAttributeType, it gets a bit buggy. When I add it to my NSManagedObject subclass header file (in the tutorial, that would be Run.h) as
#property (retain) BOOL *booleanProperty;
compiling fails, saying error: property 'booleanProperty' with 'retain' attribute must be of object type.
It seems like some places in Cocoa use NSNumber objects to represent booleans, so I tried setting it to
#property (retain) NSNumber *booleanProperty;
instead. However, this evokes the following runtime errors:
*** -[NSAttributeDescription _setManagedObjectModel:]: unrecognized selector sent to instance 0x101b470
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSAttributeDescription _setManagedObjectModel:]: unrecognized selector sent to instance 0x101b470'
Using GDB, I am able to trace this back to the line in my source code where I add my entity to the managed object model:
[DVManagedObjectModel setEntities:[NSArray arrayWithObjects:myEntityWithABooleanAttribute, myOtherEntity]];
So my question is this: what type should I set booleanProperty to in my custom class header?
Try:
#property (nonatomic) BOOL booleanProperty;
The problem was that you used the retain in the property definition. For that you must have a property for an Objective-C class (it should be able to understand the 'retain' method). BOOL is not a class but an alias for signed char.
I wouldn't recommend the method suggested by Diederik Hoogenboom (i got an error even though my core data attribute was set as Boolean).
It's worth pointing out that although this line will work for a custom object, it will not work for a subclass of NSManagedObject:
#property (nonatomic) BOOL booleanProperty;
Your property should be set as this:
#property (nonatomic, retain) NSNumber *booleanProperty;
When i copy the method declarations for a Boolean type (using the technique suggested by Jim Correia), the getter and setter are typed as:
NSNumber:-(NSNumber *)booleanProperty;
-(void)setBooleanProperty:(NSNumber *)value;
...this is what a Boolean property in core data is set as, and you need to validate your property with something like this:
-(BOOL)validateBooleanProperty:(NSNumber **)toValidate error:(NSError **)outError
{
int toVal = [*toValidate intValue];
if ( (toVal < 0) || (toVal > 1) )
{
NSString *errorString = NSLocalizedStringFromTable(#"Boolean Property", #"TheObject", #"validation: not YES or NO");
NSDictionary *userInfoDict = [NSDictionary dictionaryWithObject:errorString forKey:NSLocalizedDescriptionKey];
NSError *error = [[[NSError alloc] initWithDomain:NSCocoaErrorDomain code:-1 userInfo:userInfoDict] autorelease];
*outError = error;
return NO;
}
return YES;
}//END
…remember to include the validateBooleanProperty declaration in the header file. The setter and getter methods store and retrieve your property using -(id)primitiveValueForKey:(NSString *)key.
Finally you need to explicitly call the validate method from whatever view controller / app delegate you're setting the object from:
NSNumber *boolProp = [[[NSNumber alloc] initWithInt :0] autorelease];
NSError *valError = nil;
if ([TheObject validateBooleanProperty:&boolProp error:&valError] == YES)
{
[TheObject setBooleanProperty :boolProp];
}
In the header,
#property (nonatomic, retain) NSNumber *booleanProperty;
In the implementation,
#dynamic booleanProperty;
To set it to true...
self.booleanProperty = [NSNumber numberWithBool:YES];
To set it to false...
self.booleanProperty = [NSNumber numberWithBool:NO];
To compare it to a literal true boolean:...
self.booleanProperty.boolValue == YES;
To compare it to a literal false boolean:...
self.booleanProperty.boolValue == NO;
For more information: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/nsnumber_Class/Reference/Reference.html#//apple_ref/occ/clm/NSNumber/numberWithBool:
One of the best ways to generate correct accessors in your NSManagedObject subclass is to bring up the contextual menu on a attribute or property in the data modeling tool and choose one of the following commands:
Copy Method Declarations to Clipboard
Copy Method Implementations to Clipboard
Copy Obj-C 2.0 Method Declarations to Clipboard
Copy Obj-C 2.0 Method Implementations to Clipboard
Let Xcode 4.0 decide for you.
In Xcode: select an Entity from your *.xcdatamodel file view.
Select Editor>Create NSMagedObject Subclass...
Xcode declares your Boolean objects as type NSNumber.
Edit: I'm curious what the motivation is for mitigating the xcdatamodel? Anyone?
An attribute of type Boolean in a NSManagedObject is of type NSCFBoolean. This is a private subclass of NSNumber.
I don't know if this is just a typo on your part, but this:
[NSArray arrayWithObjects:myEntityWithABooleanAttribute, myOtherEntity]
is definitely wrong. The last parameter of that method should always be nil.
Use NSNumber. There's no bool in the CoreData framework.