I am making an iPhone app that gets data from a web service and stores it in Core Data. All these properties have keys to identify them in a dictionary. I have an NSObject class of all the properties.
I have now decided to add one more property not being gotten from a web service called checkMark. I have also added it to this method. The problem is whenever I try and set the checkMark property in a method like this : [s setValue:[NSNumber numberWithInt:check] forKey:#"checked"];, s being a managedObject I get an error saying "the entity Course is not key value coding-compliant for the key "checked". How do I fix this?
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
// Set the property values
_iD = [[dictionary valueForKey:#"Id"] intValue];
_isCurrent = [[dictionary valueForKey:#"IsCurrent"] boolValue];
_checkMark = [[dictionary valueForKey:#"checked"] intValue];
}
}
You need to make the entity Course key value coding-compliant for the key "checked".
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueCoding/Articles/Compliant.html
Basically that means it must have a setter setChecked: and a getter checked. The usual thing is to have a property with synthesis of that setter and getter.
Incidentally, the fact that the log message is talking about an entity Course suggests to me that the thing you are calling dictionary and casting as an NSDictionary might in fact not be a dictionary at all, but might be something else, i.e. an NSManagedObject. Just a guess... You might do some logging / breakpointing to see what's really happening here.
Related
First of all I want to point out that yes there are a lot of questions on this subject on stack overflow but none that was of any help. I also tried asking the owners of these for advice but was unable to get in touch with any of them.
Here is my scenario. I'm receiving data from an API which is an array of objects. These object are all the same structure but they change dynamically from API end point. When I made an NSArray of NSDictionary and tried to set my grid data source with the value of the provided array. It didn't work. When I looked at the documentation IGGridViewDataSourceHelper I found out the following piece of information "As of right now, the data must be of a derivation of NSObject and have at least one property". So I started thinking of a way to create an NSObject at run time. I was able to find some resource on Apple Developers documentation to make that.
Given that the variable dictionary is given in a function
Kindly check the following
- (NSArray *)getRecrodsFromDictionary: (NSDictionary*)dictionary {
// the following include the array that I want to turn into objects
NSArray * response = [self parseKey:#"responseDetails" fromDictionary:dictionary];
NSMutableArray * rows = [[NSMutableArray alloc] init];
if ([response count] != 0) {
// 1. get all NSDictionary keys
NSDictionary * temp = response[0];
NSArray * keys = [temp allKeys];
// 2. create a class
Class ModelClass = objc_allocateClassPair([NSObject class], "WidgetDetailsModel", 0);
// 3. all class variables with the same name as key retrieved from NSDictionary
for (NSString * key in keys) {
NSString * currkey = [key capitalizedString];
const char * name = [currkey UTF8String];
class_addIvar(ModelClass, name, sizeof(id), rint(log2(sizeof(id))), #encode(NSString));
}
// 4. register a class to be used
objc_registerClassPair(ModelClass);
for (NSDictionary * curr in response) {
// create object
id MC = [[ModelClass alloc] init];
for (NSString * key in keys) {
// set values
const char * name = [key cStringUsingEncoding:NSASCIIStringEncoding];
Ivar CurrVar = class_getInstanceVariable(ModelClass, name);
NSString * newValue = [curr objectForKey: key];
object_setIvar(MC, CurrVar, newValue);
}
// add object to array
[rows addObject:MC];
}
}
return [rows copy];
}
Once I get the return value and try to set it to data source data variable I get the following run time error.
[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key AssetsClass.
I can't find any thing on how to make the created in runtime NSObject key value coding-compliant. How can I make it key value coding-compliant?
Edit 1:
I managed to bypass the runtime error by making the fields names capitalized.
Now the table is being populated with empty data (same number of rows as the data but empty text in it) which was the correct thing to happen because the values of the iVar is not retained. How Can I retain it?
Edit 2:
I'm still not able to retain the iVar value so I changed the location of the function to the same UIView class which then it did retain it for the short period of time I had to set the grid data source data value.
I'm curious to know if there is a way to make the iVar retained or set one of its attribute to be strong/retain to mark it for the deallocation process.
After long search on Google, StackOverFlow and other iOS related forums and research. Here is the conclusion that I was able to find. Ivar in objective-c will always be weak reference. In other words there is no way (that I can find) that makes the Ivar strong reference. This can only be achieved throw property with setting the attribute of each property made.
#interface MyClass: NSObject
#property NSArray *arr;
#end
#inplementation MyClass
- (instancetype) init
{
if(self = [super init])
{
self.arr = [[NSArray alloc] init];
}
return self;
}
#end
int main(int argc, char *argv[])
{
MyClass *temp = [[MyClass alloc] init];
[temp valueForKey:#"arr.count"]; //count is ivar of NSArray
return 0;
}
then console says
NSExceptions: [MyClass valueForUnfinedKey:] this class is not key
value-complaint for the key arr.count
Everytime I use dot seperations, this exceptions come out.
I tried to search web and read menu, I still don't know why, could anyone help? Thanks.
The method valueForKey: takes a single key (property or local variable) name, it does not take a key path such as your arr.count.
The method valueForKeyPath: does take a key path, it effectively is a sequence of valueForKey: calls. See Getting Attribute Values Using Keys in About Key-Value Coding.
However you example will still not work due to the way valueForKey: is defined for NSArray:
Returns an array containing the results of invoking valueForKey: using key on each of the array's objects.
So in your case if you try valueForKeyPath:#"arr.count", the arr part of the path will return your array and then NSArray's valueForKey: will attempt to get the count key for each element of the array and not for the array itself. Not what you want...
Which brings us to Collection Operators which provide key Paths which do operate on the collection, array in your case, and not its elements. The collection operator you need is #count giving you the key path arr.#count, so you need to call:
[temp valueForKeyPath:#"arr.#count"]
Unless this is an exercise in learning about KVC this can be shortened to:
temp.arr.count
which doesn’t have the issue of trying to apply count to the array’s elements, and returns an NSUInteger value rather than an NSNumber instance.
HTH
It's because arr.count is not key value-complaint of MyClass. When program runs, it cann't find any property of MyClass name arr.count.
valueForKeyPath: - Returns the value for the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key—that is, for which the default implementation of valueForKey: cannot find an accessor method—receives a valueForUndefinedKey: message.
Let's say I have a collection of objects of different types/classes, i.e. an NSArray.
I know that all of this objects inherit from NSManagedObject and all of them have a property named "uuid".
Now, I want to loop over this array, retrieve each objects uuid and add it to another array, like this:
NSMutableArray *objectUUIDs = [[NSMutableArray alloc] initWithCapacity:0];
for (NSObject *object in objects) {
// somehow cast the object to its class, so that I can send get its uuid
}
Actually, I could check for the class by [object class] in an if-else-clause, and cast it respectively, but as I have 30 something classes, I would like to do something more generic, like (in pseudo code):
// give me the object's true class instead of NSManagedObject
// add the object uuid to my objectUUIDs array
As you say all objects are NSManagedObjects and all have a property uuid, you can use Key-Value-Coding
NSMutableArray *objectUUIDs = [#[] mutableCopy];
for (NSManagedObject *object in objects) {
[objectUUIDs addObject: [object valueForKey:#"uuid"]];
}
or
NSArray *objectUUIDs = [objects valueForKey:#"uuid"];
NSArray's -valueForKey doc
if you enumerate over an collection of objects of different classes you shouldn't type the enumerated object NSObject, but use either the closest common super class, or id — the generic objective-C object type. The reason is that you can send any message to an object typed with id, and you can do further testing. With other more concrete classes you must ensure a message is under stud by an given method.
You state
// give me the object's true class instead of NSManagedObject
The object doesn't change during casting. if you put a instance of MyFabulousManagedObject (subclass of NSManagedObject) in an array, and later you cast it to NSManagedObject, it is actually still an instance of MyFabulousManagedObject
While vikingosegundo's solution is probably best, there's also this possibility:
Define a protocol which has this property (or anything you know for sure is shared among all the objects):
#protocol FooBarProtocol
#property NSUUID *uuid;
#end
Now iterate over the original array as such:
NSMutableArray *objectUUIDs = [NSMutableArray array];
for (id<FooBarProtocol> object in objects) {
[objectUUIDs addObject:object.uuid];
}
Here, we're simply casting the objects all to objects that conform to FooBarProtocol, although it may be true that all of your objects already conform to a protocol that defines this property, or perhaps all have a common superclass with this property defined.
The main point here is that you just need to cast them to anything that defines the property.
Note that as written, this will crash if the object actually doesn't have a uuid property. Might be a good idea to add:
if ([object respondsToSelector:#selector(uuid)]) {
[objectUUIDs addObject:object.uuid];
}
And this also avoids having a massive chain of if statements to check all the different classes. We don't care what sort of class it is. We only care that it can give us a UUID.
If we replace a NSDictionary instance variable with a NSMutableDictionary that we create, can we later use it again as a NSMutableDictionary by casting it as a NSDictionary?
Example:
create and store the NSMutableDictionary into the NSDictionary slot
NSMutableDictionary *muta = [[NSMutableDictionary alloc] initWithObjects:NSArray forKeys:NSArray];
Object.nsDictionary = muta;
Get the dictionary later
NSMutableDictionary *muta2 = (NSMutableDictionary*) Object.nsDictionary;
//Do stuff like Add objects with it
[muta2 setObject:id forKey#"key"];
Do we have to recreate a NSMutableDictionary from the NSDictionary we pull from the object or does it retain it's "mutability"? Can you please tell me why a subclassed object will or will not retain its specific methods and properties when replacing a generic super class?
If your property is declared as NSDictionary then you shouldn't make any assumptions about whether it is actually mutable or not.
The proper code should be:
NSMutableDictionary *muta2 = [Object.nsDictionary mutableCopy];
This works regardless of what type of dictionary is actually stored in the property.
In your question you are confusing two different things: you refer to assigning to an instance variable but show code which shows assigning to a property. These are not the same. You are also appear to be misunderstanding assignment by referring to it as replacing an object.
In Objective-C (and many other, but not all, languages) an object is referred to by a reference. It is these references which are assigned into variables. So for example in:
NSMutableDictionary *a = [NSMutableDictionary new];
NSMutableDictionary *b = a;
The right hand side of the first assignment is an expression which creates a new object and returns a reference to that object. This reference is then stored into the variable a. The second line copies the reference, not the object, stored in a and stores into into the variable b.
After these two lines one object and two variables have been created, and both variables reference exactly the same object. Assignment of a reference to an object does not change the object it refers to. So if we now change the code to:
NSMutableDictionary *a = [NSMutableDictionary new];
NSDictionary *b = a;
We still have one object and two variables created, and both still refer to exactly the same object. The assignment in this case is allowed as NSMutableDictionary is a subclass of NSDictionary - that is an object of type NSMutableDictionary is also of type NSDictionary, it provides all the same behaviour as the latter.
From your question "Can you please tell me why a subclassed object will or will not retain its specific methods and properties when replacing a generic super class?" you need to read up on inheritance and understand how subclassing works.
Once you've stored a reference to a subclass into a superclass typed variables, a into b in the above code, while you haven't changed the referenced object in anyway you have lost the immediate knowledge that the reference is in fact to an object of the subclass - all you can immediately state about a reference stored in b above is that it refers to an object which is at least an NSDictionary, but may be of any subclass of NSDictionary.
If you are absolutely sure, or just like writing programs that break, you can tell the compiler to trust you that b contains a reference to an NSMutableDictionary by using a cast:
NSMutableDictionary *c = (NSMutableDictionary *)b;
Do this and the compiler trusts you, but if b does not contain a reference to an NSMutableDictionary then your subsequent usage of c will probably be invalid and your program will break.
What you need to do is to test whether the reference refers to an NSMutableDictionary or not, and you do this with the method isKindOfClass::
if ([b isKindOfClass:NSMutableDictionary.class])
{
// b refers to an NSMutableDictionary
NSMutableDictionary *c = (NSMutableDictionary *)b;
// do something with c
}
else
{
// b does not refer to an NSMutableDictionary
// handle this case
}
Back to properties: a property is two methods (assuming read-write, you can have read-only properties), a getter and a setter, which combine to provide an abstraction of a variable - you can "read" and "assign" to them using dot notation in expressions. However as they call a method, rather than performing direct reads or assignments to a variable, that method can change was is read or assigned. In particular an object typed property declared with the copy attribute will make a copy of the object that is reference. For example:
#property (copy) NSDictionary *c;
...
NSMutableDictionary *a = [NSMutableDictionary new];
NSDictionary *b = a;
self.c = a;
then a & b both refer to the same object which is an instance of NSMutableDictionary; while, due to the copy attribute,crefers to a *distinct* object which is an instance ofNSDictionary`.
You can now see why using instance variable and property interchangeably in your question is not right - what is assigned can be different depending on whether the assignment is to a variable or a property.
You should read up on objects, object references, inheritance and properties.
HTH.
Why does the method [NSString stringWithFormat] return an id type? From the name I'm expecting it returns a NSString, not a generic pointer. Other classes follow this "rule". For example [NSNumber numberWithInt] returns a NSNumber, not an id.
I think it's not even justified from the fact that is something like a factory method.
You mention NSNumber, this doesn't have any direct subclasses, so it's safe for numberWithInt: to return a NSNumber*
NSString* (and other classes such as NSSet) return type id because they can have subclasses (NSMutableString and NSMutableSet respectively), which will inherit this method. As such, these inherited calls need to return an instance of the subclass's type, and due to the rules of method naming, you can't overload based on return type alone. As such, they need a common type between them all, which is id.
Update: Recent developments mean the keyword instancetype is now available. You may have noticed a lot of references to id are now replaced with instancetype. This keyword provides a hint to the compiler that the return type of the method is the same class of the receiver of the method.
For example (and I stress an example, this may not be the case in the actual framework):
NSArray:
- (instancetype)initWithArray:(NSArray*)array;
NSMutableArray:
// Inherits from NSArray
----
// Receiver is NSArray, method belongs in NSArray, returns an NSArray
[[NSArray alloc] initWithArray:#[]];
// Receiver is NSMutableArray, method belongs in NSArray, returns an NSMutableArray
[[NSMutableArray alloc] initWithArray:#[]];
Because it is a static method on the class NSString and it is assumed to be returning the type of the object being called on. This is also dependent on the type since this can come from an NSMutableString.