I have a JSON response that I turn into a Dictionary like so:
NSError *error;
self.restKitResponseDict = [NSDictionary dictionaryWithDictionary:[NSJSONSerialization JSONObjectWithData:response.body options:0 error:&error]];
I have a core data class that has the following attributes/properties:
name
image_url
When I log the restKitResponseDict from above I see that image_url is listed as "image_url" like this:
name = Rock;
"image_url" = "http://f.cl.ly/items/122s3f1M1E1p432B211Q/catstronaut.jpg";
Is this why KVC is crashing on
[CoreDataClass setValuesForKeysWithDictionary:self.restKitResponseDict];
like this:
'[<CoreDataClass 0x14132c> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key image_url.'
Do the quotes matter? Should I ask my server guy to get rid of the underscore that's likely causing it?
Core Data Class:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface CoreDataClass : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * image_url;
#end
#implementation CoreDataClass
#dynamic name;
#dynamic image_url;
#end
You are sending the method to the class object:
[CoreDataClass setValuesForKeysWithDictionary:self.restKitResponseDict];
when you probably want to send it to the actual CoreDataClass instance:
[coreDataClassObject setValuesForKeysWithDictionary:self.restKitResponseDict];
EDIT
what's the simplest way to init the object from the class? – Eric
It's a subclass of NSManagedObject, so you use the normal Core Data methods. One way to create a new object:
CoreDataClass *coreDataObject = [NSEntityDescription
insertNewObjectForEntityForName:#"YOUR_ENTITY_NAME"
inManagedObjectContext:managedObjectContext];
If you need basic information about using Core Data, see the Core Data Programming Guide: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/coredata/cdProgrammingGuide.html#//apple_ref/doc/uid/TP30001200-SW1
The NSLog() with the %# format uses the description method to print objects, and the description of a NSDictionary puts quotation marks around all keys and values that contain any special characters, such as the underscore. For example
NSDictionary *dict = #{
#"key1" : #"value_1",
#"key_2" : #"value2"
};
NSLog(#"dict=%#", dict);
produces
2012-08-25 18:15:33.553 test27[3416:c07] dict={
key1 = "value_1";
"key_2" = value2;
}
Therefore the key in your JSON dictionary does not really have quotation marks, and the underscore is probably not the cause of the error.
The error message indicates that the managed object does not have an image_url attribute, therefore you should check that.
Related
I want to create a "business object" based on a NSDictionary. The reason for this is that I want implementations to be able to extend this object with arbitrary keys, and another reason is that I am persisting it using the convenient plist format (the objects stored are either integers, floats or strings).
The business object contains a number of predefined properties, e.g.
#property NSString* customerName;
#property NSString* productCode;
#property int count;
#property double unitPrice;
I want to serialize this, for example to a property list (this is not a strict requirement, it could be some other easy-to-use format). Preferably, the implementation of the class should be just
#synthesize customerName, productCode, count, unitPrice:
for the example above. To use this class, I want to do something like:
MyBusinessObject* obj = [MyBusinessObject businessObjectWithContentsOfFile:fileName];
obj.productCode = #"Example";
[obj setObject:#"Some data" forKey:#"AnExtendedProperty"];
[obj writeToFile:fileName atomically:YES];
You should make your class KVC complaint. KVC does the magic. Look here.Ex,
// assume inputValues contains values we want to
// set on the person
NSDictionary * inputValues;
YOURCLASS * person = [[YOURCLASS alloc] init];
[person setValuesForKeysWithDictionary: inputValues];
The "path of least resistance" turned out to be using NSCoding instead.
How to copy NSString data to my custom object's NSString property?
I have an object question with NSString *text and KDoctor *doctor properties.
KDoctor is an object with two properties: NSString *name and UIImage *photo.
self.question.doctor.name=#"abc";
NSLog(#"doctorname: %#", question.doctor.name);
Output is:
doctorname: (null)
Why? How could I solve this problem?
my guess is that question.doctor != self.question.doctor
or
question == nil
or
question.doctor == nil
It seems you have something wrong declaring the properly. Are you defining "question" property like this?
In your header:
#class KonsQuestion
#interface YourClass : NSObject {
KonsQuestion * _question;
}
#property(nonatomic, retain) KonsQuestion * question;
In the implementation file:
#implementation YourClass
#synthesize question = _question
#end
In this case you should use always self.question to use the getter and setters generated and use [_question release] in the dealloc method
I have the following array
NSMutableArray* answers;
Each element of answers is itself an array of objects.
I need to convert the above 2D array into appropriate JSON format (using the JSONKit framework), so that it can be passed to a php application and decoded thereafter...
The individual objects have the following structure:
#interface Answer : NSObject {
//NSString* answerId;
NSString* answer;
NSString* questionId;
}
//#property (nonatomic, retain) NSString* answerId;
#property (nonatomic, retain) NSString* answer;
#property (nonatomic, retain) NSString* questionId;
#end
That is that each element of answers is an array of Answer Objects. essentially what I need is to encode the relevant data in each answer object into JSON format using the JSONKit framework so that it can be posted to a php app and decoded....
Essentially I need somthing of the form:
{{"answer":"1","questionId":"1"}, {{"answer":"5","questionId":"2"},......}
The JSONKit, like all other JSON frameworks, does not play well with custom objects. To that end, you need to iterate through your object and put them into objects that the JSONKit can understand (NSArrays and NSDictionaries). Something like this:
NSMutableArray *jAnswers = [[[NSMutableArray alloc] init] autorelease];
for(Answer *answ in answers)
{
NSMutableDictionary *jAnswer = [[[NSMutableDictionary alloc] init] autorelease];
[jAnswer addObject: answ.answer forKey: #"answer"];
[jAnswer addObject: answ.questionId forKey: #"questionId"];
[jAnswers addObject: jAnswer];
}
NSString *jAnswersJSONFormat = [jAnswers JSONString];
would give you:
[{"answer": "1", "questionId": "1"}, {"answer": "5", "questionId": "2"}, ...]
JSONKit does seem to offer both a delegate-based and a block-based method for serializing unsupported object types. My guess, not having used the framework, is that you call one of those versions of the serialization methods and pass a delegate/selector pair or a block which returns a JSON-serializable object in place of an unsupported object type.
You'll want one of these category methods on NSArray:
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
I'm fetching some objects out of a data store but the results aren't what I'm expecting. I'm new to CoreData but I'm fairly certain this should work. What am I missing?
Note that User is a valid managed object and that I include its header file in this code, and that UserID is a valid property of that class.
NSFetchRequest *requestLocal = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:messageManagedObjectContext];
[requestLocal setEntity:entity];
// Set the predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY UserID IN %#", userList];
[requestLocal setPredicate:predicate];
// Set the sorting
... sorting details removed but exist and are fine ...
// Request the data
NSArray *fetchResults = [messageManagedObjectContext executeFetchRequest:requestLocal error:&error];
[requestLocal release];
for (int i; i < [fetchResults count]; i++) {
[fetchResults objectAtIndex:i].UserID = ...<----HERE
}
Isn't fetchResults an array of User objects? Wouldn't [fetchResults objectAtIndex:i] be a User object? Why do I get an error when building that "request for member 'UserID' in something not a structure or union"?
Sorry if this is a basic error, I'm clearly missing some basic concept. I've done a ton of searching and it seems like it should be right. (I also tried fast enumeration but it complained that fetchResults items weren't valid Objective C objects, effectively the same error, I think.)
Update:
(from comment below)
My goal is to update the object, calling saveAction after changing it.
Does the KVC method still refer to the actual object? I tried fast enumeration with:
for (User thisUser in fetchResults) {
... but it didn't like that.
I used the more generic version:
(id thisUser in fetchResults)
...but it won't let me set
[thisUser valueForKey:#"FirstName"] = anything
... insisting that there's no Lvalue.
Will:
[[thisUser valueForKey:#"FirstName"] stringWithString:#"Bob"]
... do the trick or is there a better way? Sorry, I know it's nearly a new question, but I still don't get what is in the fetchResults array.
Your fetchedResults variable contains a NSArray object. However, a NSArray can hold any arbitrary group of objects. Unlike a standard C array, there is no requirement that the NSArray objects all be of a single class.
The dot notation you are using here:
[fetchResults objectAtIndex:i].UserID =
... while a legal syntax, nevertheless confuses the compiler because the compiler has no idea what class of object is returned by [fetchResults objectAtIndex:i]. Without knowing the class it has no idea what the heck UserID is. Hence the error "request for member 'UserID' in something not a structure or union". At the very least you have to cast the return of [fetchResults objectAtIndex:i] to some class so that the complier has a clue as to what 'UserID' is.
However, you simply shouldn't use this construction even though it legal because it is dangerous. See below for the best practice form.
Understanding NSManagedObject and its subclasses can be tricky because NSManagedObject itself uses a trick called associative storage which allows any generic NSManagedObject instances to store any property of any entity defined in any model. This can confuse novices because there are multiple ways to refer to the same entities, instances and properties. Sometimes the examples use generic NSMangedObjects and setValue:forKey:/valueForKey: and other times they use objectInstance.propertyName.
Associative storage works like a dictionary attached to every instance of the NSManagedObject class. When you insert a generic NSManagedObject like this:
NSManagedObject *mo=[NSEntityDescription insertNewObjectForEntityForName:#"User"
inManagedObjectContext:self.managedObjectContext];
... you get an instance of the NSManageObject class whose associative storage keys are set to the properties of the User entity as defined in your data model. You can then set and retrieve the values using key-value coding (which has the same syntax as dictionaries) thusly:
[mo setValue:#"userid0001" forKey:#"UserID"];
NSString *aUserID=[mo valueForKey:#"UserID"];
Associative storage allows you represent any complex data model in code without having to write any custom NSManagedObject subclasses. (In Cocoa, it allows you to use bindings which let you create entire programs without writing any data management code at all.)
However, the generic NSManagedObject class is little better than a glorified dictionary whose saving and reading is handled automatically. If you need data objects with customized behaviors you need to explicitly define a NSManagedObject subclass. If you let Xcode generate the class from the entity in the data model you end up with a source file something like:
User.h
#interface User : NSManagedObject
{
}
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * userID;
#property (nonatomic, retain) NSString * lastName;
#end
User.m
#import "User.h"
#implementation User
#dynamic firstName;
#dynamic userID;
#dynamic lastName;
#end
Now, you are no longer limited by to the key-value syntax of associative storage. You can use the dot syntax because the complier has a class to refer to:
User *aUser=[NSEntityDescription insertNewObjectForEntityForName:#"User"
inManagedObjectContext:self.managedObjectContext];
aUser.userID=#"userID0001";
NSString *aUserID=aUser.userID;
With all this in mind, the proper forms of reference to the fetchedResults array become clear. Suppose you want to set all userID properties to a single default value. If you use the generic NSManagedObject class you use:
for (NSManagedObject *aMO in fetchedResults) {
[aMO setValue:#"userid0001" forKey:#"UserID"];
NSString *aUserID=[aMO valueForKey:#"UserID"];
}
If you use a dedicated subclass you would use:
for (User *aUserin fetchedResults) {
aUser.userID=#"userID0001";
NSString *aUserID=aUser.userID;
}
(Note: you can always use the generic form for all NSManagedObject subclasses as well.)
Accessing your CoreData attributes by property Accessors (dot notation) will only work if you have defined a custom NSManagedObject subclass in your Model and defined properties on that class. The implementation should be #dynamic. You'll then have to cast the object to the proper class:
//Assume this exists:
#interface User : NSManagesObject
{
}
#property (nonatomic, retain) NSString* UserID;
#end
#implementation User
#dynamic UserID
#end
// You could do:
for (int i; i < [fetchResults count]; i++) {
((User*)[fetchResults objectAtIndex:i]).UserID = ... // This works
}
Or you may use KVC to access your models properties like this (without needing a class):
for (int i; i < [fetchResults count]; i++) {
[[fetchResults objectAtIndex:i] valueForKey:#"UserID"] = ... // This too
}
You would set the value using [object setValue:newValue forKey:#"UserID"] please note, that newValue needs to be an object in general and one of NSString, NSNumber, NSDate, NSSet for CoreData.
Two additional thoughts:
Your could and should use fast Enumeration on the results array:
for (id object in fetchResults) {
[object valueForKey:#"UserID"] = ...
}
I do not understand the ANY keyword in your predicate. "UserID IN %#" should do as well.
Your basic problem is that -objectAtIndex: returtns an object of type id. No accessors are defined for type id so when you use dot notation with the object returned by -objectAtIndex: the compiler assumes you mean to access a C structure member. id is a pointer type, not a structure type, hence the error you are getting.
The whole core data stuff is a red herring with regard to this issue. You'd get the same error if User was derived from NSObject and you had populated the array yourself manually.
The ways out of it are:
Use fast enumeration
for (User* aUser in theArray)
{
....
}
which is the preferred idiom if you need to iterate through the whole array
Cast the result of -objectAtIndex: to the correct type.
((User*)[theArray objectAtIndex: i]).userId;
Use the message sending syntax instead of dot notation
[[theArray objectAtIndex: i] setUserId: ...];
Personally, I'd go with 1 and 3.
for (User* aUser in theArray)
{
[aUser setUserId: ...]
}
Clearly any of the above are dangerous if you are not certain that the objects in the array are User objects. You can use -respondsToSelector: to make sure it will work if you like.
I'm moving my initial steps in the Core Data realm (and I'm quite new to iPhone development, too) and I found a behavior I cannot explain.
I declared a subclass of a NSManagedObject and defined a few properties, some of them of type NSString *, MyObject.h is something like:
#interface MyObject : NSManagedObject {
}
#property (nonatomic, retain) NSString *contentFile;
#property (nonatomic, retain) NSString *contentPath;
#property (nonatomic, retain) NSDate *creationDate;
#property (nonatomic, retain) NSString *name;
#end
Now, if I try to create an object instance and assign a value to the name property, when I try to print back the content of the property it seems it's mangled.
In the AppDelegate, where the whole Core Data stack is defined, I write:
MyObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:#"MyObject"
inManagedObjectContext:self.managedObjectContext];
newObject.name = #"Testing";
NSLog([NSString stringWithFormat:#"Name: %s\n", newObject.name]);
What I get in the output console is
2009-08-24 20:03:55.176 MyApp[15727:20b] Name: ‡}00»
I can't understand if I'm doing something wrong or if I forgot something. Anyone can help, please?
You need to use the %# format specifier in stringWithFormat:, since NSString is an Objective-C object:
NSLog([NSString stringWithFormat:#"Name: %#\n", newObject.name]);
%s is used for C-strings (char*s). For more info look at String Format Specifiers.
The correct format specifier for Objective-C objects is %#, not %s.
You should use %#, not %s. %s is for char* strings. You're passing in an objective-c object.