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
Related
#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.
I have a class called
Contact;
In Contact I have (simple version to test, no hash yet)
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return NO;
}
I do:
NSMutableArray *arr = [[NSMutableArray alloc] init];
Contact *contact = [Contact new];
[arr addObject:contact]
// I expect my isEqual to be called here but it does not get called
[arr containsObject:contact] // this evaluates to true somehow!!!
However if I add a another object to type NSString, then it gets called for comparing String object but not for the contact object. Which means
[arr addObject:#""] // so now arr has two elements
// here I expect two calls to isEqual but only one gets there
// when comparing string object against Contact
[arr containsObject:contact]
Why is isEqual not getting called in cases I mentioned above??
Please read the discussion about isEqual: in the NSObject Protocol in the Reference Library.
You'll find that for objects which are inside a collection (such as an NSArray), hash might be used to determine whether two objects are actually the same. If two pointers are actually pointing to the same object, there is no need to check for equality - hence isEqual: never gets called.
The solution as suggested by the reference library is to implement hash in your subclass as well.
I am trying to check if the NSMutableArray has a specific object, before adding the object to it, if exists then don't add.
i looked over many posts explaining how to do this, managed to implement it like this, but it always gives me that the object "doesn't exist", though i already added it !
//get row details into FieldLables Object
AllItemsFieldNames *FieldLabels = feedItems[row];
// object to hold single row detailes
AllItemsFieldNames *SelectedRowDetails = [[AllItemsFieldNames alloc] init];
SelectedRowDetails.item_name = FieldLabels.item_name;
//SelectedRowDetails.item_img = FieldLabels.item_img;
SelectedRowDetails.item_price = FieldLabels.item_price;
//NSLog(#"item has been added %#", SelectedRowDetails.item_name);
//NSLog(#"shopcartLength %lu", (unsigned long)SelectedFieldsNames.count);
if([SelectedFieldsNames containsObject:SelectedRowDetails])
{
NSLog(#"Already Exists!");
}
else
{
NSLog(#"Doesn't Exist!");
[SelectedFieldsNames addObject:SelectedRowDetails];
}
I can display all object from the NSMutableArray into a table, what i need to do in the above code is stop the addition of duplicate objects.
The first method listed on the NSArray documentation under the section "querying an array" is containsObject:. If it's not working, that suggests that your implementation of isEqual: is not correct. Make sure you follow the note in the documentation:
If two objects are equal, they must have the same hash value. This
last point is particularly important if you define isEqual: in a
subclass and intend to put instances of that subclass into a
collection. Make sure you also define hash in your subclass.
You might also consider using an NSSet since you can't add duplicates to that. Of course, this would also require a working version of isEqual:.
Sets are composed of unique elements, so this serves as a convenient way to remove all duplicates in an array.
here some sample,
NSMutableArray*array=[[NSMutableArray alloc]initWithObjects:#"1",#"2",#"3",#"4", nil];
[array addObject:#"4"];
NSMutableSet*chk=[[NSMutableSet alloc ]initWithArray:array]; //finally initialize NSMutableArray to NSMutableSet
array= [[NSMutableArray alloc] initWithArray:[[chk allObjects] sortedArrayUsingSelector:#selector(compare:)]]; //after assign NSMutableSet to your NSMutableArray and sort your array,because sets are unordered.
NSLog(#"%#",array);//1,2,3,4
I have my own class with several properties and I have them in NSArray. I need to use them for method which takes NSArray of strings. So I am asking what is best aproach to get array with strings from my array which has custom classes. I can create second array and use it but I think there could be better way. I need to have it for different custom classes (from one, I want to use for example name property to new NSArray, in second title property and so).
I hope I explained well but I tried it once more on example:
NSArray *arrayWitCustomClasses = ... fill with custom classes;
// setValues method takes NSArray with NSStrings
// when arrayWithCustomClasses used it returns copyWithZone: error on custom class
[someObject setValues:[arrayWithCustomClasses toArrayWithStrings]];
As long as your object exposes the required values as NSString properties you can use the valueForKey method of NSArray.
For example
NSArray *arrayOfTitles=[arrayWithCustomClasses valueForKey:#"title"];
NSArray *arrayOfNames=[arrayWithCustomClasses valueForKey:#"name"];
Or
[someObject setValues:[arrayWithCustomClasses valueForKey:#"title"]];
and so on
NSMutableArray *strings = [NSMutableArray array];
for (NSObject *item in arrayWithCustomClasses) {
/* You can use a different property as well. */
[strings addObject:item.description];
}
[someObject setValues:strings.copy];
Like #Tim says, but you could shorten it by just using:
[someObject setValues:[arrayWithCustomClasses valueForKey:#"description"]];
Same result. One line of code.
Then implement the description method of your custom classes to return whatever properties and formatting you want.
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