I have some code that was originally written as:
+(double)averageOfArray:(NSArray*)array fromIndex:(int)from toIndex:(int)to{
if (to < 0) return 0;
if (to > array.count) return 0;
double sum = 0.0;
for (long i = from; i < to; i++){
sum += [array[i] doubleValue];
}
return sum/(double)(to-from);
}
But I am trying to write it so that it's more efficient. I don't know if this will be faster or not, but one idea is to create a subarray and then calling valueForKeyPath:#"sum.self" like this:
...
NSArray *t = [array subarrayWithRange:NSMakeRange(from, to-from)];
double sum = [[t valueForKeyPath:#"sum.self"] doubleValue];
return sum/(double)(to-from);
But this is throwing an error:
Thread 1: Exception: "[<__NSCFNumber 0xd99f8c3636814118>
valueForUndefinedKey:]: this class is not key value coding-compliant
for the key sum."
Looking at the debugger it shows my NSArray *t as t = (__NSArrayl_Transfer *) #"500 elements". But when I look at an array that I've created like this NSArray *testArray = #[#0.1, #0.2, #0.3, #0.4 #0.5, #0.6]; then that shows up as as testArray = (__NSArrayl *) #"6 elements". I'm assuming that for some reason these underlying types are where the problem is.
I've tried creating a new array by doing this:
NSArray *t = [[NSArray alloc] initWithArray:[array subarrayWithRange:NSMakeRange(from, to-from)] copyItems:YES];`
But that does not fix the issue. What am I failing to understand?
Using Collection Operators:
When a key path contains a collection operator, any portion of the key path preceding the operator, known as the left key path, indicates the collection on which to operate relative to the receiver of the message. If you send the message directly to a collection object, such as an NSArray instance, the left key path may be omitted.
The portion of the key path after the operator, known as the right key path, specifies the property within the collection that the operator should work on. All the collection operators except #count require a right key path.
Operator key path format
keypathToCollection.#collectionOperator.keypathToProperty
|_________________| |_________________| |_______________|
Left key path Operator Right key path
You're sending the message directly to a collection object, you can omit the left key path.
Collection operator always starts with #.
Right key path is required (except #count).
Because you forgot to use # prefix, the following code ...
double sum = [[t valueForKeyPath:#"sum.self"] doubleValue];
... tries to get sum from every NSNumber * in the array. And then you get:
Thread 1: Exception: "[<__NSCFNumber 0xd99f8c3636814118> valueForUndefinedKey:]: this class is not key value coding-compliant for the key sum."
Just add the # prefix to the collection operator and you'll be fine:
double sum = [[t valueForKeyPath:#"#sum.self"] doubleValue];
Related
Say I have an array of arrays like:
NSArray *array = #[#[#1, #2], #[#3, #4], #[#5, #6]];
Is there a KVC key path that would give me #[#1, #3, #5] ?
To my surprise, there is, although it relies on some undocumented behavior.
The documentation for NSArray's override of -valueForKey: says it builds a new array by applying -valueForKey: to each element with the provided key and returns that. It does not suggest that any keys are special.
However, NSDictionary's override does say that keys starting with # are treated specially. Instead of looking at the contents of the dictionary, it looks at the properties of the dictionary itself for one matching the key stripped of the leading #. So, you could use [someDict valueForKey:#"#count"] to get the count of objects in the dictionary.
As it turns out, NSArray actually respects the same convention. You can use [array valueForKey:#"#firstObject"] to get #[#[#1, #2]] in your example. Now the question is, how do you get that to apply to the inner arrays, not the outer array.
First, what happens if you just call [array valueForKey:#"firstObject"] without the # on the key? The outer array calls [innerArray valueForKey:#"firstObject"] on each of the inner arrays. Each inner array, in turn, tries to call [element valueForKey:#"firstObject"] on each of its elements. However, its elements are NSNumber objects which aren't KVC-compliant for the key "firstObject". So that blows up.
But, KVC supports collection operators with -valueForKeyPath: (note the "Path" on the end of that). One operator is #unionOfObjects. This applies the part of the path to the right of the operator to the elements of the array and then returns an array of the results. This is similar to the case where -valueForKey: propagates to the inner arrays, except you get to specify a different key for the inner arrays.
So, combining these mechanisms, you can do: [array valueForKeyPath:#"#unionOfObjects.#firstObject"] to get #[#1, #3, #5]. The outer array applies [innerArray valueForKeyPath:#"#firstObject"] to each inner array. Because of the leading #, that does not propagate to the elements of the inner arrays. It just gets the firstObject of the inner arrays. Then the outer array combines those into a result array.
KVC key path work only when Object is confirming Key Value coding compliance . means object , scaler value should have setter and getter along the key name. In you structure there is no key for index path having getter and setter. So I have created custom getter method for satisfy NSKeyValueCoding for inner object index path. this will work for each index of inner array.
#interface NSArray (KVO)
- (id) unionOfObjectsForKeyPath:(NSString*)keyPath;
#end
#implementation NSArray (KVO)
- (id) unionOfObjectsForKeyPath:(NSString*)keyPath
{
NSMutableArray * values = [NSMutableArray new];
for (id obj in self) {
if ([obj isKindOfClass:[NSArray class]]) {
#try {
NSUInteger index = [keyPath integerValue];
id value = nil;
if (index < [(NSArray *)obj count]) {
value = [obj objectAtIndex:index];
if(value && value!=[NSNull null] )
{
[values addObject:value];
}
}
}
#catch (id) {}
}
}
return [NSArray arrayWithArray:values];
}
#end
In VC :
self.myArray = #[#[#1, #2], #[#3, #4], #[#5, #6]];
NSArray *testArray =[self.myArray unionOfObjectsForKeyPath:#"0"];
NSLog(#"test array : %#", testArray);
I have an array, pulled from JSON received from a web service. When I use NSLOG to view the contents of the array, it displays like this:
{ ? Category = "New Products";
CategoryID = 104;
SubCategories = (
);
}
I need to take the Category and CategoryID values and assign them to the managed object "newProductCategory". I have no problem assigning the Category value, which corresponds to a string type, and I have no problem assigning a hard-coded number to the int 32 type that's supposed to receive the Category ID. But I've been struggling when it comes to converting the CategoryID value into anything that will be accepted as the int 32 type.
What's the proper syntax for converting that value into something digestible for this line of code, in place of the zero?
[newProductCategory setValue : 0 forKey : #"productCategoryID"];
Here's one of my failed attempts that might be informative. When I try...
// Pull category record "i" from JSON
NSArray * categoryProperties = [categories objectAtIndex:i];
NSNumber * productCategoryID = [categoryProperties valueForKey:#"CategoryID"];
... then I attempt to assign it in the above format, using productCategoryID in place of the zero, it produces the following error:
'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "parentCategoryID"; desired type = NSNumber; given type = __NSCFString; value = 104.'
Even if you specify int32 in CoreData, you will pass a NSNumber object, and it seems that you get a NSString from the json parsing (you can try a log of NSStringFromClass([productCategoryID class]) to be sure).
You can try :
NSString * productCategoryID = [categoryProperties valueForKey:#"CategoryID"];
newProductCategory.productCategoryID = #([productCategoryID intValue]);
//or
newProductCategory.productCategoryID = [NSNumber numberWithInt:[productCategoryID intValue]];
You need to set NSNumber, there are 2 ways:
[newProductCategory setValue:#(0) forKey:#"productCategoryID"];
or
[newProductCategory setValue:[NSNumber numberWithInt:0] forKey:#"productCategoryID"];
I'm currently working on a project where the user defines some parameters in a NSDictionnary, that I'm using to setup some objects.
For example, you can ask to create a Sound object with parameters param1=xxx, param2=yyy, gain=3.5 ... Then an Enemi object with parameters speed=10, active=YES, name=zzz ...
{
active = NO;
looping = YES;
soundList = "FINAL_PSS_imoverhere_all";
speed = 100.0;
}
I then instantiate my classes, and would like to set the ivars automatically from this dictionnary.
I've actually wrote some code to check that this parameter exists, but I'm having trouble in actually setting the parameter value, especially when the parameter is non object (float or bool).
Here's what I'm doing so far :
//aKey is the name of the ivar
for (NSString *aKey in [properties allKeys]){
//create the name of the setter function from the key (parameter -> setParameter)
NSString *setterName = [aKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[aKey substringToIndex:1] uppercaseString]];
setterName = [NSString stringWithFormat:#"set%#:",setterName];
SEL setterSelector = NSSelectorFromString(setterName);
//Check if the parameter exists
if ([pge_object respondsToSelector:setterSelector]){
//TODO : automatically set the parameter
}
else{
[[PSMessagesChecker sharedInstance]logMessage:[NSString stringWithFormat:#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]] inColor:#"red"];
NSLog(#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]);
}
}
}
As you can see, I don't know what to do once I've found that the parameter exists on the object. I tried to use "performSelector... withObject..., but my problem is that some of the parameters are non-objects (float or bool).
I also tried to get the class of the parameter, by using the setter, but it didn't help.
Did anyone manage to do something like that?
Jack Lawrence's comment is spot on.
What you are looking for is called Key Value Coding, or just KVC.
This fundamental part of Cocoa lets you get and set any instance variable using its name as a String and a new value.
It will automatically handle coercing Objects to primitive values, so you can use it for int and float properties too.
There is also support for validating values and handling unknown properties.
see the docs
your code, without validation, could be written
for( id eachKey in props ) {
[anOb setValue:props[eachKey] forKey:eachKey];
}
or just
[anOb setValuesForKeysWithDictionary:props];
as Jack said.
For the non-object parameters you have to put them into an object, for example NSNumber or NSValue. You can then add these objects into your dictionary.
For Example:
float f = 0.5;
NSNumber f_obj = [NSNumber numberWithFloat:f];
I have a Property list = DictionaryofAddresses, inside which is an NSDictionary StoredAddr;
The keys inside StoredAddr are Mac Addresses, and the values an array of RSI readings.
I have an array, compareAddr (which holds an array of Mac addresses)
I want to use a loop, in which the value at compareaddr[i] will be used as a key, to match whether addresses in my array match any keys in my dictionary.
NSArray *compareAddr = [DiscoveredAP allKeys];
for (int i = 0; i < [compareAddr count]; i++){
NSArray *RSIatAddr = [[dictionaryOfAddresses objectForKey:#"StoredAddr"]objectForKey:compareAddr[i]];
The following code gives me an error :
expected identifier
Thank you for any help
Use [compareAddr objectAtIndex:i] instead of compareAddr[i]
Im currently devising a solution for my object BuildingNode *tower which is held inside NSMutableArray *gameObjects, to attack EnemyNode *enemy objects also held inside the gameObjects array.
I have a proposed solution which occasionally causes my game to freeze (temporarily) as the solution employed is quite buggy.
My solution is that each tower object contains its own NSMutableArray *targets which is synthesized from the Tower Class. If an enemy comes into range or out of range of any given tower object, the corrosponding index of the EnemyNode *enemy object from the gameObjects array is saved as an NSNumber into the targets array, or alternatively if the enemy object is out of range, it is removed from the .targets array.
Basically the idea is that the targets array holds the indices of any enemy that is in scope of the tower.
The problem I seem to be facing is that because this tower.targets array is updated dynamically all the time, i believe that if i'm doing some sort of operation on a particular index of tower.targets that then is removed, i get this error:
-[BuildingNode distance]: unrecognized selector sent to instance 0x2dd140
Each BuildingNode *tower has different attacking alogrithms that use the tower.targets array for calling back the sorted/desired enemy.
For example, a random attack style will randomize a number between 0 & [tower.targets count] then I can create a pointer to gameObjects with the corresponding [tower.targets intValue].
Something like this:
EnemyNode *enemy = (EnemyNode *)[gameObjects objectAtIndex:[[tower.targets objectAtIndex:rand]intValue]];
So this will find a random enemy from the potential .targets array and then create a pointer object to an enemy.
I've put in many if statements to ensure that in the case of a .targets index being removed mid-sorting, the operation shouldnt go ahead, thus removing the game failure rate, but it still occurs occassionally.
Heres my code:
Please note that BuildingNode *tower == BuildingNode *build.
This is just a snippet of the iteration inside gameObjects.
//Potential Enemies (Indices) Add and Delete/
if (enemy.distance < build.atk_distance && !enemy.isExploding){
NSNumber *index = [NSNumber numberWithInt:[array indexOfObject:enemy]];
if(![build.targets containsObject:index]){
[build.targets addObject:index];
}
}
else {
NSNumber *index = [NSNumber numberWithInt:[array indexOfObject:enemy]];
if ([build.targets containsObject:index]){
[build.targets removeObject:index];
}
}
}
//Aiming// Nearest Algorithm.
//Will find the nearest enemy to the building
if (enemy.distance < build.atk_distance){
if (!build.isAttacking || build.atk_id == 0){
if ([build.targets count]){
if ([build.atk_style isEqualToString:#"near"]){
int l_dist;
for (int i = 0; i < [build.targets count]; i++){
//Grab the Enemy from Targets
if ([build.targets objectAtIndex:i]){
if([array objectAtIndex:[[build.targets objectAtIndex:i]intValue]]){
EnemyNode *temp = [array objectAtIndex:[[build.targets objectAtIndex:i]intValue]];
if (temp){
int c_dist = temp.distance;
if (!l_dist || c_dist < l_dist){
l_dist = c_dist;
build.atk_id = temp.uniqueID;
build.isAttacking = YES;
}
}
}
}
}
}
}}
Unrecognized selector sent = calling a method on an object that doesn't exist. In this case, you're calling the distance method/property getter on a BuildingNode object.
The only distance I see is on your temp object, which is retrieved by EnemyNode *temp = [array objectAtIndex:[[build.targets objectAtIndex:i]intValue]]; That indicates you are really pulling a BuildingNode object out of the array instead of an EnemyNode. Find out why that happens.