I have two functions:
one that returns an array that is filled in a block
- (NSArray *)getArray {
NSArray *someValues = #[#0, #42, #23, #5, #8, #2013];
NSArray *filter = #[#42, #23, #5];
//replacing this NSMutableOrderedSet with a NSMutableArray
//and return just matched then, resolves the problem.
//so the exception has to do something with that set.
NSMutableOrderedSet *matched = [[NSMutableOrderedSet alloc] init];
for (id value in someValues) {
[filter enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isEqual:value])
[matched addObject:value];
}];
}
return [matched array];
}
and another one that enumerates the returned array from the first method
- (void)enumArray:(NSArray *)array {
NSEnumerator *enumerator = [array objectEnumerator];
for (id obj in enumerator) {
if ([obj isEqual:#42])
[enumerator nextObject]; // <== this line causes the error!
}
}
If i now do something like that
NSArray *array = [foo getArray];
[foo enumArray:array];
i will get a NSGenericException with following message:
Collection <__NSOrderedSetArrayProxy: 0x123456> was mutated while
being enumerated
where the hell is something mutated. i don't get it. returning a copy from that array solves the problem, but i still don't get it.
The error has do something with the NSMutableOrderedSet, if i replace the set with an array i don't get an exception.
some screenshots, of exception thrown
You are using fast enumeration while altering an enumerator instance.
Basically it is a big no-no to modify an object that you fast enumerate over (that form of the for loop you are using uses fast enumeration). However, you use [enumerator nextObject]; to access the next object from the enumerator, but this modifies the enumerator by removing the current object from it. So it is your use of nextObject within a for...in loop that is mutating the enumerator.
Get past this problem quickly by using a while loop instead of the for loop, a bit like this:
- (void)enumArray:(NSArray *)array {
NSEnumerator *enumerator = [array objectEnumerator];
while ((id obj = [enumerator nextObject])) {
if ([obj isEqual:#42])
[enumerator nextObject];
}
}
This should get past the fast enumeration/mutation problem. Note, I have absolutely no idea why you want to move the enumerator on a step when obj is equal to 42, but am presuming within the context of the entire code-base that this makes sense!
The basic reason is, you can't edit/modify a mutable array while you're going through it.
So here are the two solutions,
1.Please use #synchronized() directive to lock the array while you mutate it.
- (void)enumArray:(NSArray *)array {
NSEnumerator *enumerator = [array objectEnumerator];
for (id obj in enumerator)
{
if ([obj isEqual:#42])
{
#synchronized(enumerator)
{
[enumerator nextObject]; // <== this line causes the error!
}
}
}
}
2.Just do a copy of you NSArray and use it
- (void)enumArray:(NSArray *)array {
NSEnumerator *enumerator = [[array copy] objectEnumerator];
for (id obj in enumerator)
{
if ([obj isEqual:#42])
{
[enumerator nextObject]; // <== this line causes the error!
}
}
}
Related
I have two array, called array1 and array2. I would like to remove every object from array1 that's value of the "nameId" key can be find in both array. Actually I'm trying it in a for loop, but it doesn't make sense. It doesn not crash, it just simply calls the log in the else statement, that I don't understand why happens. Maybe somebody could show me the right solution.
NSMutableArray *newArray = [self.array1 mutableCopy];
for (PFObject * object in newArray) {
PFObject *placeholderObject = object;
for (PFObject *object2 in self.array2) {
if ([placeholderObject[#"nameId"] isEqualToString:object2[#"nameId"]]) {
[self.array1 removeObject:object];
NSLog (#"EXISTING OBJECT FOUND %#", object);
} else {
NSLog(#"UNIQUE OBJECT FOUND %#", idO[#"hirCime"]);
}
}
}
When creating a mutableCopy of an array you create a new array with a copy of every object in it but they aren't the same ones, so object is a member of newArray but is not a member of self.array1 so you can't remove it from that array.
This should work:
// Creates a new empty mutable array
NSMutableArray *newArray = [#[] mutableCopy];
for (PFObject *object in self.array) {
BOOL found = NO;
for (PFObject *object2 in self.array2) {
if ([object[#"nameId"] isEqualToString:object2[#"nameId"]]) {
found = YES;
NSLog (#"EXISTING OBJECT FOUND %#", object);
break;
} else {
NSLog(#"UNIQUE OBJECT FOUND %#", idO[#"hirCime"]);
}
}
if (!found) {
[newArray addObject:[object copy]];
}
}
// And maybe you want this
self.array = newArray;
It's hard to explain why I need index of duplicate elements in array. When I tried to fetch the index of element in traditional way it shows only one index, but I need to fetch the all index of duplicate values
for ex:
NSArray *array=#[#"one",#"one",#"one",#"two",#"two",#"four",#"four",#"four"];
int index = [array indexOfObject:element];
NSLog(#"index %d",index);
here if I try to fetch index of " one " it shows index is 0 but I need to get further indexes of one
You can fetch the index of duplicates like this:
NSArray *array=#[#"one",#"one",#"one",#"two",#"two",#"four",#"four",#"four"];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if ([obj isEqualToString:#"one"])
{
NSLog(#"index %d",idx);
}
}];
int i,count=0;
for (i = 0; i < [array count]; i++) {
if element == [array objectAtIndex:i] {
indices[count++] = i;
}
}
Declare an empty array indices, and indices will contain all the indices of the given element.
NSString *element = #"one";
NSArray *array=#[#"one",#"one",#"one",#"two",#"two",#"four",#"four",#"four"];
NSIndexSet *matchingIndexes = [array indexesOfObjectsPassingTest:^BOOL(NSString *obj, NSUInteger idx, BOOL *stop) {
return [obj isEqual:element];
}];
[matchingIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSLog(#"%ld", (long)idx);
}];
Ultimately I don't think the NSArray methods are going to help you here, so you're going to have to write some pretty basic code. There is probably a cleaner answer, but here is a fairly simply solution to the problem.
This just goes through the array, and creates an NSDictionary for each unique number. It assumes the array is sorted as your example was, so simply checks the prior index's value against the current index to see if they have changed. When they change, it knows it's done with that value and saves the dictionary to an array.
NSArray *array=#[#"one",#"one",#"one",#"two",#"two",#"four",#"four",#"four"];
NSString *priorString = array[0];
NSMutableDictionary *duplicatesByKey = [[NSMutableDictionary alloc] init];
NSMutableArray *indexesOfDuplicates = [[NSMutableArray alloc] init];
int index = 0;
for (NSString *string in array) {
if ([priorString isEqualToString:string]) {
[indexesOfDuplicates addObject:[NSNumber numberWithInt:index]];
} else {
[duplicatesByKey setObject:indexesOfDuplicates forKey:priorString];
indexesOfDuplicates = [[NSMutableArray alloc] init];
[indexesOfDuplicates addObject:[NSNumber numberWithInt:index]];
}
priorString = string;
index ++;
}
[duplicatesByKey setObject:indexesOfDuplicates forKey:priorString];
I hope that helps.
Use
NSCountedSet * countedSet = [NSCountedSet setWithArray: array];
and
NSSet * uncountedSet = [NSSet setWithArray: array];
-- to create a counted set from your array, and a conventional NSSet.
Then:
[countedSet minusSet: uncountedSet];
countedSet will now contain only elements for the duplicates (if any), and the countForObject: method will return the number of duplicates (in excess of 1) for that element.
I currently have a NSArray which contains many NSArrays, each containing a pair of NSStrings such like the following: [["A", "B"], ["U", "A"], ["X", "Y"], ...], and I am interested first checking to see if it contains a particular object, and then grabbing the other paired object and putting it in an array. For example, if I am checking for "A" in the above array, the result array would contain ["B", "U"]
I know how to iterate over each array, but am trouble deciding how to grab the paired object inside the array... thanks!
for (NSArray *innerArray in outerArray){
if ([innerArray containsObject: #"A"]){
//how to extract the other object and save it to an array?
}
}
NSMutableArray *results = [NSMutableArray array];
for (NSArray *innerArray in outerArray){
// Get the index of the object we're looking for
NSUInteger index = [innerArray indexOfObject:#"A"];
if (index != NSNotFound) {
// Get the other index
NSUInteger otherIndex = index == 0 ? 1 : 0;
// Get the other object and add it to the array
NSString *otherString = [innerArray objectAtIndex:otherIndex];
[results addObject:otherString];
}
}
Should do the trick.
If you're sure that your data will have exactly the structure you describe, you can use the fact that inner array have exactly 2 element - so index of "other" element will be 1-indexOfYourElement:
for (NSArray *innerArray in outerArray){
NSUInteger ix = [innerArray indexOfObject:#"A"];
if (ix!=NSNotFound){
id objectToAdd = innerArray[1-ix];
// Do something with it
}
}
Here's one possible way:
NSMutableArray* results = [[NSMutableArray alloc] init];
for (NSArray *innerArray in outerArray){
if ([innerArray containsObject: #"A"]){
[results addObjectsFromArray: [innerArray enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL *stop) {
if (![obj isEqual: #"A"])
{
[results addObject: obj];
}
}]];
}
}
I am new to iOS development, I encounter error when replaceObjectAtIndex. Any wrong with my codes? Please help.Thanks.
self.myArray =array;
for (NSDictionary *data in array) {
NSString *fbid = [data objectForKey:#"id"];
for (int index = 0; index < self.myPersonArray.count; index ++) {
for (IP_PERSON *person in self.myPersonArray) {
if ([person.UserDef2 isEqualToString:fbid]) {
[self.myArray replaceObjectAtIndex:index withObject:person];
break;
}
}
}
Error is :
Terminating app due to uncaught exception NSGenericException, reason: '*** Collection <__NSArrayM: 0xa34f6c0> was mutated while being enumerated.
You cannot use fast enumeration and mutate collection at the same time, hence the error message. You can resort to using an usual for-loop.
You're iterating over array, which is equal to self.myArray.
Further down, you're editing this array when you do: [self.myArray replaceObjectAtIndex:index withObject:person];
To resolve this, just make self.array a mutableCopy of the original array:
self.myArray = [array mutableCopy];
You can take another temporary array and iterate over that array, so you are enumerating and mutating different array.
NSArray *tempArray = [yourArray copy];
for (IP_PERSON *person in tempArray) {
if ([person.UserDef2 isEqualToString:fbid]) {
[self.myArray replaceObjectAtIndex:index withObject:person];
break;
}
}
[tempArray release];
Alternatively you can iterate without an enumerator, you can use regular for loop with starting index, exit condition and increment as you have done in outer loop.
You can..
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]
self.myArray =array;
for (NSDictionary *data in array) {
NSString *fbid = [data objectForKey:#"id"];
for (int index = 0; index < self.myPersonArray.count; index ++) {
for (IP_PERSON *person in self.myPersonArray) {
if ([person.UserDef2 isEqualToString:fbid]) {
[dictionary setObject:person forKey:#(index)]; //Notice this line
break;
}
}
}
}
And then..
for(id key in dictionary) {
[self.myArray replaceObjectAtIndex:[key intValue] withObject:[dictionary objectForKey:key]];
}
based on the fact that you cannot edit mutable Collections while enumerating them, this is the best solution i could come up with to edit a Array of NSMutableDictionaries:
__block NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithCapacity:1];
__block NSUInteger idx;
[_myArray enumerateObjectsUsingBlock:^(NSMutableDictionary* obj,
NSUInteger indx, BOOL *stop) {
if (// some condition is met) {
tempDict = [obj mutableCopy];
idx = indx;
}
}];
[tempDict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"];
[_myArray replaceObjectAtIndex:idx withObject:rowSelected];
this seems way too complicated (even for a language like obj-c).. and since it's involving two data types (NSMutableArray and NSMutableDictionary), it doesn't seem like I can cleanly put them into a category.. advice?
update: one comment asked why do I create a mutablecopy (as opposed to just a copy.. since it's copying a mutable object)..
suppose I just used copy.. if i put a break on tempDict this is what I get:
// tempDict = [obj copy]
po tempDict
$0 = 0x0b28cc10 <__NSArrayI 0xb28cc10>(
1
)
// tempDict = [obj mutableCopy]
po tempDict
$0 = 0x0b28cc10 <__NSArrayM 0xb28cc10>( //notice the M in __NSArrayM as opposed to I above
1
)
in case of copy.. if I follow it with a line like this:
[tempDict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"];
I get this error:
[__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0xb245100
I get the same above error with this code:
for (NSUInteger idx = 0; idx < [_myMutableArray count]; idx++) {
NSMutableDictionary* myMutableDict = _myMutableArray[idx];
[myMutableDict setObject:obj forKey:key];
}
update 2:
the origin of the problem was instantiating non mutable arrays and dictionaries.. I'm new to the whole new obj-c literals, so I didn't know that to create a NSMutableArray and NSDictionary, you gotta do this, respectively:
[#[..] mutableCopy]
[#{..} mutableCopy]
So in your case, I don't quite follow why you call tempDict = [obj mutableCopy]; when from the conditions you write the dictionary is already writable.
You can use several tricks. Like using
for (NSUInteger idx = 0; idx < _myArray.count: idx++_ {
NSMutableDictionary *obj = _myArray[idx];
// modify
}
For NSDictionaries you can get allKeys and iterate over that copy. This is a bit slower than using fast enumeration, but still faster than doing workarounds like boxing integers to replace later :)
In your case you are NOT modifying the array at all only the dictionaries within the array. There are no contstraits on how you modify the objects within the array. Here is a bit of equivalent code:
for (NSMutableDictionary *dict in _myArray) {
if (someCondition)
[dict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"]
}
You would have a problem if you absolutely needed to replace the object in your array. In that case, if the array is not huge I would suggest the same as #Markus. Iterate over a copy and modify the original.
Maybe you can use KVC and do :
NSArray<NSMutableDictionary *> *result = [[_myArray filteredArrayUsingPredicate:[NSPredicate withFormat:#"{YOUR CONDITION}"]] valueForKey:#"mutableCopy"];