How NSSet compares values in array to remove duplicates when using SetWithArray - objective-c

I have NSArray and I want to remove duplicates from it. I know that using this method
[NSSet setWithArray:[arrAllValues valueForKey:#"value"]]
I also know that it invokes method specified in valueForKey parameter. But I don't know what method NSSet invokes to compare objects in array.
My problem is that I want to compare property named "value", but I want to return array of the object that contains the property and not the property. Can I do that?
Thanks !!!

It compares them using the NSObject protocol reference method:
- (BOOL)isEqual:(id)anObject
therefore, you can override this method in your class to implement the desired behavior, as a note just like you would do it in Java, you should also override hash.

Related

Checking if object exist in object array without looping

I would like to check if my NSMutableArray contains my custom object. But if I understand correct contains functions searches for the same object in array (placed at the same memory point)
if(![objectArray containsObject:objToCheck])
{
[objectArray addObject:objToCheck];
}
I know that objectArray has identical object with identical variable values compared to objToCheck, yet such if always returns false. Is there a way to check this without writing custom loop and comparing objects by their parameters?
Override the [NSObject isEqual:] method (actually it's part of the NSObject protocol) of your custom object and check whatever instance variables make sense to you for an object to be considered equal.
Here's an Apple Cocoa Competency article on the subject.
You might try creating a temporary NSSet from your array and testing against that for membership.

Does NSMutableSet's addObject method replace a duplicate or ignore insertion?

I am struggling to figure if whether adding an already existing object to a set in Cocoa actually replaces the object or simply ignores addObject: if there is a duplicate. I am using a custom object that is considered the same as another object if a specific field is equal.
I am overriding both isEqual: and hash methods and containsObject: does return true when I call it, but I would like to update the set with the new object and for some reason it doesn't work if I call addObject:.
If it does ignore it, what would be the best data structure to use in place of NSMutableSet in order to have the desired effect?
From the description, it ignores if there is a duplicate.
Adds a given object to the set, if it is not already a member.
You could cast the NSMutableSet to a CFMutableSet, which has methods that allows greater control on how to add objects (you want CFSetSetValue):
CFSetSetValue: The value to be set in theSet. If this value already exists in theSet, it is replaced.
CFSetAddValue: If value already exists in the collection, this function returns without doing anything.
CFSetReplaceValue: If this value does not already exist in theSet, the function does nothing.
NSMutableSet's -addObject: method will not add an object which passes the -member: test, as such it doesn't do anything.
If you still want to update the set with the new object regardless, you can use an NSMutableArray, on which you'd call -replaceObjectAtIndex:withObject::
#interface NSMutableArray(JRAdditions)
- (void)addUniqueObject:(id)object;
#end
#implementation NSMutableArray(JRAdditions)
- (void)addUniqueObject:(id)object {
NSUInteger existingIndex = [self indexOfObject:object];
if(existingIndex == NSNotFound) {
[self addObject:object];
return;
}
[self replaceObjectAtIndex:existingIndex withObject:object];
}
#end
It returns without doing anything. NSMutableSet is toll-free bridged to CFMutableSet, and from the docs for that class:
If value already exists in the collection, this function returns
without doing anything.
I'm sorry, but you are missing the idea of "equality". If you need to add an object to a set because it's different, then the "is equal" operator needs to return it is different.
The ONLY valid time to replace an object in a set would be if it was entirely identical. Basic set semantics.
What you are describing is not a set if you are trying to replace an object with a different object. That is a different type of keyed collection. I would probably suggest the most logical one being a basic dictionary. key = value. So then you just assign the dictionary["objectId"] = object and it will naturally add or replace.
You need to use the right tool for the job.

Is the NSSet iteration needed in this case?

I have a NSSet of object from the same class and I want to get the property value for each object stored in the NSSet, do I always need to iterate through the NSSet ? Or can I somehow create another NSSet containing the properties of the objects only without iterating?
If I have a NSSet of strings, and I need to remove the same substring from all strings in the NSSet, do I need to iterate, or can I create another NSSet with the removed substrings in another way ?
thanks
Try calling your NSSet's valueForKey:. It returns a set of the property of every item in your set.
This requires every object in the set to be KVC compliant.

Objective-c return method returning NSMutableArray instead of declared NSArray return type

If I want to return an immutable array like this + (NSArray *)ids but inside this method I'm declaring a NSMutableArray because I want to sort it using -sortUsingSelector:.
Returning this method works perfect.
But is it "okay" to write code that declares that the return method should be one type and the actually type is another?
Does this work because NSMutableArray is a subclass of NSArray?
Is the actual return value an NSArray or an NSMutableArray?
(…) is it "okay" to write code that declares that the return method should be one type and the actualy type is another?
Yes, it is provided the types are compatible; see the next paragraph. In fact, there are some cases in Cocoa (notably class clusters) that do that as well.
Does this work because NSMutableArray is a subclass of NSArray?
Exactly. Since NSMutableArray is a subclass of NSArray, it inherits all its methods and declared properties, so it publicly behaves like NSArray. This is called the Liskov substitution principle.
Is the actual return value a NSArray or a NSMutableArray?
The return value is whatever you’re returning. Since you’re returning an NSMutableArray, it’s an NSMutableArray. From the caller perspective, it is an object that can be used like an NSArray.
Whether it's "okay" or not is a difficult question. If it's a small project and only you use the code, it might be allright. However, if you rely on nothing being able to change the "NSArray" returned, it's not ok. (See the last point)
Yes.
It's an NSMutableArray. A simple cast will let you change the values in it.
If you actually want to a return an immutable object, do the following:
NSArray* immutableArray = [[mutableArray copy] autorelease];
return immutableArray;

Implementing a category within the implementation of another interface in Obj-C

I have a custom class in Obj-C called RouteManager which contains an array of NSStrings. Each string is a bus stop name which is used as a key for a dictionary to get the rest of the information for the bus stop (basically, just [busStopDictionary allkeys]). In one of the situations where my app uses this array, I want to return the array sorted by the distance from the user. I've started setting up the code to be able to call sortedArrayUsingSelector on my array with the following method:
- (NSComparisonResult)compareByDistance:(NSString*) otherStop
{
// Return appropriate NSOrdered enum here based on comparison of
// self and otherStop
}
My problem is that in the case where compareByDistance is a method of RouteManager, self refers to the instance of RouteManager. However, I need self to refer to the NSString that the compare is being called on. So, I assumed I needed to setup a category, as such:
#interface NSString (Support)
-(NSComparisonResult) compareByDistance:(NSString*)otherStop;
#end
This got my self reference correct, however this comparison uses values from the RouteManager class. When implemented as seen above, the NSString (Support) implementation obviously complains that those values are undeclared.
That should provide enough background info for my question. How do I go about doing this? I would like my category of NSString, which consists solely of the method compareByDistance, to be able to use values from the current instance of my class, RouteManager, which inherits from NSObject. Ideally, I feel as though the category should somehow be within RouteManager. I feel there has to be some way to accomplish this that is cleaner than passing the necessary values into compareByDistance. Thanks in advance for any and all help.
Your best bet would be to define a custom class for a bus stop, instead of storing them as strings and dictionaries.
Make the BusStop class have properties for Name, Location and whatever else. Implement the compareByDistance: method on the BusStop class.
You can still use a dictionary if you need to look them up by name. Just store them with the name as the dictionary's key, and the BusStop object as the dictionary's value.