I want to add items to mutable array from a dictionary. Problem is I want to check existing array items before adding new item. If same item is already there in the array, I want to replace it. else add the new item.
How could I do it?
You could perhaps use an NSMutableSet rather than an NSMutableArray. The addObject method on NSMutableSet will only "add a given object to the set, if it is not already a member."
If you'd like to check membership before adding to the set anyway, you can check the result of:
[mySet containsObject:myObjectFromDictionary]
...which returns a simple BOOL value indicating whether the set already contains an object whose isEqual method returns true when your object is passed to it.
(For a little extra functionality, NSCountedSet will keep track of the number of objects added to the "set" for which isEqual: returns true)
You could compare the result of : [yourArray indexOfObject:yourObject]; against NSNotFound to know if the object is in the array.
It will give you the index of the object to replace, or if it is equal to NSNotFound, you will add it.
Objects equality is tested with isEqual: method.
NSArray class reference.
On the face of it, both Vincent's and Rich's answers are correct.
However, there is a conceptual issue in the original question that hasn't been addressed.
Namely, that "membership in an array" via indexOfObject: (or containsObject: in a set) is ultimately done by comparing the two objects using isEqual:.
If isEqual: returns YES, then the two objects better had damned well be functionally identical in your code or else you have other, significantly more serious, problems in your design and implementation.
Thus, the real question should be "How do I detect if an object is already in an array and not add it?" and Rich's and Vincent's answer are both still correct.
I.e. you should only need to check for presence and, if present, take no action.
(Note that there are esoteric situations where replacement is actually warranted, but they are both truly esoteric and not generally used within the context of a mutable collection)
Related
I have a mutable array that contains the sounds that are being played.
I have a continous process that parse that array to adjust volumes and some other things.
Sometimes, a new sound is played and must be added to that array just before it's play starts.
And sometimes, I have a crash because my array "was mutated while being enumerated".
How may I solve that ?
You can't easily change an array while it's enumerating.
Enumerate through the array and note the new sound to be added (using a variable, or a separate array if you need to note more than one). When the enumeration is finished, add the sound to the array.
Alternatively, make a copy of the array, enumerate the copy and add the sound to the original one when you need to.
It would be nice to see any code here. But according what you are saying, i think the problem lies in the way that you use to iterate through the array. I guess it looks like this:
for ( type *object in myArray) {
...
}
Now, as you exception tells you, you can't modify the array while doing this. If you, on the other hand, access the array's values via the indexes, it should work:
for (int i = 0; i < myArray.count; i++) {
[myArray objectAtIndex:i]...
}
Keep in mind however, that the indexes aren't 'stable' that way, especially if you remove objects.
I have an 3 NSMutableArray objects that contain CMTime objects. How can I iterate through all three of them in an efficient manner and find out if there are duplicate values in all three? For example, I'm iterating through one of time and reading the value and storing it in x. Now, I want to see if x occurs (at any position) within the other two arrays. I tried looking for a contains method, but couldn't find one. I did come across filterUsingPredicate, but I'm not sure if this is the best way of doing it nor how to actually use predicates.
I tried looking for a contains method, but couldn't find one.
Use indexOfObject:
like this:
if ([array indexOfObject:object] != NSNotFound) {
// object found
}
else {
// object not found
}
You can use ([yourArray indexOfObject:x] != NSNotFound) in place of your missing contains method. However, if you're doing this quickly, often, or with a lot of elements, you should consider using NSMutableOrderedSet, which is ordered like NSMutableArray, but offers a quick and efficient contains method, as well as allowing quick operations like union and intersection, which might allow you to redesign your algorithm to iterate through your elements much less.
Is there a way to tell if a certain object is in an NSArray? The way I am adding objects to my array makes it possible for the same object to be added multiple times and I wanted to see if there was a way to see if it was already there (anywhere) in that array.
The NSArray containsObject: method is precisely for this purpose, its full signature being:
- (BOOL)containsObject:(id)anObject
See the full NSArray Class Reference docs for more information.
if([yourArray indexOfObject:yourObject] == NSNotFound) {
// your object is not in here
}
Edit: middaparkas approach is way better (if you don't want the index …)!
I bring this up because objects that compare the same with isEquals: aren't necessarily identical. Some (many) objects only compare certain properties to determine equality.
That makes the exact behavior of the following NSSet methods important:
setByAddingObject:
setByAddingObjectsFromArray:
setByAddingObjectsFromSet:
The documentation does not specify what happens when the receiver and the parameter contain equivalent objects. Will the resulting NSSet contain the object from the receiver, or the object from the "other" parameter?
Note that in NSMutableSet, it DOES specify the behavior of its add methods--objects will NOT be added if an "equal" object already exists in the set.
The documentation of NSMutableSet's addObject: method used to cover a similar case:
If anObject is already present in the set, this method has no effect on either the set or anObject.
But, as you can see from following the link, the current version doesn't even say that. And even that statement really only covers trying to add the same object; it does not specifically address adding a different but equal object.
Relying on observed but not documented behavior is dangerous, not just because it can change between OS versions, but also because it can change within the very same process. That's because NSSet is a class cluster, meaning there may be multiple implementations. Which one you get depends on how you create the set; there is no way to ensure that a specific implementation will be chosen or even exist.*
That's because it shouldn't matter. Every one of the clustered subclasses presents the same behavior as defined in the cluster's interface and documentation. (If it ever doesn't, that's a bug and you should report it.) Given that all the subclasses do effectively the same things, it shouldn't matter which one you get an instance of.
The same principle applies to your objects. They're equal! For that reason, it shouldn't matter which one is in the set. If it does matter, then they are not truly equal, and you need to make the objects' definition of equality more rigid. (Don't forget to update both isEqual: and hash.)
Depending on what you're using the set for, you may want to take that even farther and ensure that no two equal objects can exist. To do this, move the ownership, maintenance, and use of the set into the member objects' class, and have it always return a matching object instead of creating a new one whenever possible and appropriate.
*And even if you could choose one of the implementations, there's no guarantee that it'd have the behavior you observed forever—it could, and Murphy says probably will, be different in another OS version.
I tested this with the following code. SomeClass is defined such that propertyA is the only property considered in hash and isEquals:
SomeClass *objectA = [[[SomeClass alloc] init] autorelease];
objectA.propertyA = #"test";
objectA.propertyB = #"objectA";
SomeClass *objectB = [[[SomeClass alloc] init] autorelease];
objectB.propertyA = #"test";
objectB.propertyB = #"objectB";
NSSet *setA = [NSSet setWithObject:objectA];
NSSet *setB = [NSSet setWithObject:objectB];
NSSet *setC = [setA setByAddingObjectsFromSet:setB];
NSLog(#"Set A\n%#", [setA description]);
NSLog(#"Set B\n%#", [setB description]);
NSLog(#"Set C\n%#", [setC description]);
The output when running this code is:
2011-03-03 16:35:15.041 temp[50311:207] Set A
{(
{SomeClass propertyA:test propertyB:objectA}
)}
2011-03-03 16:35:15.041 temp[50311:207] Set B
{(
{SomeClass propertyA:test propertyB:objectB}
)}
2011-03-03 16:35:15.042 temp[50311:207] Set C
{(
{SomeClass propertyA:test propertyB:objectA}
)}
This demonstrates that the newly created NSSet will contain objects from the RECEIVER in the case that the parameter contains equivalent objects.
EDIT - I'm marking this as the answer, because it directly answers the question at hand. I would however, point out Peter's answer below and the concerns he voices. This behavior is undocumented and as such, while it's extremely unlikely these core classes will change in this regard, it's worth pointing out that risk. If you write code assuming this behavior it is possible that it will break in a future release. Caveat emptor.
I am implementing a class that has to store arbitrary objects together with a string. i.e.
myUIViewObject, #"that's a nice view"
myUIViewController, #"not really special"
myOtherObject, #"very important one"
this list can be extended and modified at any time, so I thought about using NSMutableDictionary here. But I am not really sure...
The object should be the key, i.e. I want to find easily the matching string for myUIViewController or myOtherObject when I ask for it like so:
- (NSString*)checkObjNoteStringForObject:(id)anyObjectInList;
The other problem is, that when an object gets added to that "list", I don't want it to be retained because of that. NSMutableDictionary retains it's contents, right? Could I just send a -release afterwards to undo this unwanted behaviour, and when removing from the list just sending -retain before doing so? Or is there a more elegant way?
What do you suggest? Thanks # all!
If your dictionary key is not retained, once it is deallocated accesses to the dictionary will lead to undefined behaviour (in practice, they'll crash if a lookup happens to hit that dictionary element). To do what you want, you need a strategy to remove the objects from the dictionary when necessary.
If you do have one – for instance, overriding the objects’ -dealloc and removing them from there – you can do what you want using +[NSValue valueWithNonretainedObject:]. The NSValue will refer to your object without retaining it, and the dictionary will copy the NSValue (keys are copied, not retained). Just remember to create an NSValue for each time you want to look something up in the dictionary; a helper function or method is a good idea.