KVC - key value coding with dot seperation - Exception not KVC compliant - ios

#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.

Related

How can I make NSObject that is created in runtime key value coding-compliant

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.

NSDictionary extend valueForKey method

Can I create category that will extend - (id)valueForKey:(NSString *)key method and if the value of this key will be equal [NSNull null] it will return me empty string #"".
It is connected to nil values for some keys that I get from the backend server in JSON.
So if I need to save some value to the property it can be nil.
Suppose I use this code with value for key nil
artist.name = ad[#"artist"][#"name"]; // nil value on the right.
so if I will have extension it will check value for key and return me #"" intend of nil. Does it make sense.
Or do I need extend NSManagedObject for checking attribute type and then return corresponded value #"", 0 or nil, or 0.0f if the attribute has another not string type.
Rather than defining a category on NSDictionary, you could define a category on NSObject, and do the conversion there:
#interface NSObject (DBNullHandling)
-(NSString*)nullToEmpty;
#end
#implementation NSObject (DBNullHandling)
-(id)nullToEmpty {
return (self == [NSNull null]) ? #"" : self;
}
#end
Now you can do this:
artist.name = [ad[#"artist"][#"name"] nullToEmpty];
If ad[#"artist"][#"name"] returns [NSNull null], artist.name gets assigned #""; otherwise, it gets assigned the value returned by ad[#"artist"][#"name"].
What I do is check before performing the assignment. The tripartite operator is a good shorthand way to do this. You have:
artist.name = ad[#"artist"][#"name"];
Instead, I would write
id val = ad[#"artist"][#"name"];
artist.name = val ? val : #""; // or val ?: #""
(I have not completely understood the question, so it might be necessary to break that check down into more stages or modify it in some way to check for [NSNull null] instead of nil. But you get the idea.)
Instead of subclassing NSDictionary to change the implementation of valueForKey:, I suggest you check if the key exists. So you would instead do this:
BOOL keyExistsInDictionary = [[myDict allKeys] containsObject:key];
id objectForKey = [myDict objectForKey:key];
if (nil == objectForKey && keyExistsInDictionary) objectForKey = #"";
If objectForKey: returns nil, you can check keyExistsInDictionary to see if the key is there and simply contains [NSNull null]. This lets you differentiate between a key that exists and a key that doesn't without modifying NSDictionary.
Can I create category that will extend - (id)valueForKey:(NSString *)key method
The short answer here is no, don't do that. Categories let you extend classes by adding method, but you shouldn't use them to override existing methods. The main reason for this is that it's unreliable -- the order in which the methods in categories are added to a class isn't defined, which means that if you have two categories that override the same method, you'd have no idea which version of the method the class would ultimately end up with.
Another reason is that you have no way to refer to the original method from the overriding method, so you'd have to re-implement the behavior that you want to keep.
Finally, a category changes the class, so every instance of that class will have the new behavior. That's not usually a problem when you're adding methods -- code that doesn't use the new methods remains unaffected by the new functionality. But when you change an existing method, particularly for a class that's as widely used as NSDictionary, it's very likely that you'll break a lot of existing code.
if the value of this key will be equal [NSNull null] it will return me empty string #""
You've gotten some good answers already, but since you asked about categories, here's an approach that solves the problem using a category appropriately. Instead of trying to change -valueForKey:, use a category to add a new method called -stringForKey: that returns a string. It sounds like you're working exclusively with strings, so this seems like a reasonable solution. The implementation can call -valueForKey: and transform non-string objects into strings:
#interface NSDictionary (StringValue)
- (NSString *)stringForKey:(NSString*)key;
#end
#implementation NSDictionary (StringValue)
- (NSString *)stringForKey:(NSString*)key
{
id value = [self valueForKey:key];
NSString *string;
if ([value isKindOfClass:[NSString class]]) {
string = (NSString*)value;
}
else if ([value isKindOfClass:[NSNull class]]) {
string = #"";
}
else {
string = [value description];
}
return string;
}
#end

iOS how does NSMutableArray check if it contains NSNumber objects?

I'm writing some code that will be using NSMutableArray and storing int values within it, wrapped within NSNumbers.
I would like to confirm that querying an iOS NSArray or NSMutableArray using new NSNumbers with same values is legal, of if I need to explicitly iterate over the array, and check if each int value is equal to the value I want to test against?
This appears to work:
NSMutableArray* walkableTiles = [NSMutableArray array];
[walkableTiles addObject:#(1)];
[walkableTiles addObject:#(2)];
[walkableTiles addObject:#(3)];
if([walkableTiles containsObject:#(1)])
{
DLog(#"contains 1"); //test passes
}
if([walkableTiles containsObject:[NSNumber numberWithFloat:2.0]])
{
DLog(#"contains 2");//test passes
}
if([walkableTiles containsObject:[NSNumber numberWithInt:3]])
{
DLog(#"contains 3");//test passes
}
What you are doing is fine. Why wouldn't it be?
The containsObject: method actually iterates over the array and calls the isEqual: method on each object passing in the object you are checking for.
BTW - there is nothing special here about using NSNumber. It's the same with an array of any object type. As long as the object's class has a valid isEqual: method, it will work.
Per the Apple's NSNumber documentation, you should use isEqualToNumber:
isEqualToNumber: Returns a Boolean value that indicates whether the
receiver and a given number are equal.
- (BOOL)isEqualToNumber:(NSNumber *)aNumber

Key Value Property help IOS

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.

NSString stringWithFormat return type, why is it "id"?

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.

Resources