Getting exception as "Collection was mutated while being enumerated" - iphone

I am getting the Collection was mutated while being enumerated exception when I am using this code can any one suggest me how to get out of this.
PaymentTerms * currentElement;
for (currentElement in termsArray)
{
printf("\n currentElement Value........%s",[currentElement.days UTF8String]);
printf("\n Str value...%s",[Str UTF8String]);
NSRange range = [currentElement.days rangeOfString:Str options:NSCaseInsensitiveSearch];
if(!(range.location != NSNotFound))
{
PaymentTerms *pTerm1 = [[PaymentTerms alloc]init];
pTerm1.days = Str;
printf("\n pTerm1.days...%s",[ pTerm1.days UTF8String]);
[termsArray addObject:pTerm1];
}
}
Hope I get quick response from ur side.
Thank in advance,
Monish.

You cannot change array while you're enumerating it. As a workaround you should accumulate new objects in temporary array and add them after enumeration:
PaymentTerms * currentElement;
NSMutableArray* tempArray = [NSMutableArray array];
for (currentElement in termsArray)
{
NSRange range = [currentElement.days rangeOfString:Str options:NSCaseInsensitiveSearch];
if(!(range.location != NSNotFound))
{
PaymentTerms *pTerm1 = [[PaymentTerms alloc]init];
pTerm1.days = Str;
[tempArray addObject:pTerm1];
[pTerm1 release];
}
}
[termsArray addObjectsFromArray: tempArray];
P.S. do not forget to release pTerm1 object you create - your code contains memory leak
In respond to poster's comment (and actual task) - I think the easiest way to make bool flag indicating if day value was found in cycle. If not - add new object after cycle ends:
PaymentTerms * currentElement;
BOOL dayFound = NO;
for (currentElement in termsArray)
{
NSRange range = [currentElement.days rangeOfString:Str options:NSCaseInsensitiveSearch];
if(range.location != NSNotFound)
dayFound = YES;
}
if (!dayFound)
// Create and add new object here

This line [termsArray addObject:pTerm1];
will throw that exception. You CANNOT add/delete an element from an array inside a for each loop. for (currentElement in termsArray)

Yes...we cannot enumerate while the array is getting updated...This might be irritating for the programmers who are from ActionScript background.Some times things go worse like "You even dont get a crash or intimation at runtime when you update an array count while it is being enumerated"-The execution just behaves abnormally at that time.
Btw you can go for this type of implementation where you can have minor changes to your code.
for (int i=0 ; i< termsArray.count ;i++) //counting termsArray on every iteration
{
id currentElement = [ termsArray objectAtIndex:i];
......
.....
}
Of-course,This(i< termsArray.count) might seem bad as we are calculating the count for every iteration...And thats the trick here to have minor changes.But I would strongly recommend VLADIMIR's implementation as its clear for reading.

you are adding an object to your collection as you are looping over it, thats whats causing the error. your if statement is nested inside the for

The error occurs because you are adding new objects to termsArray within the for loop
Create a new empty array (e.g.newTermsArray)
In the first loop create and add these new items to newTermsArray
Then you will need a second loop to add the items from newTermsArray back into the original termsArray

Just dealt with the same bug in a rather complex app. The solution is simple - create a copy and work with the copy of array (and remove it from the original if you want. Then discard the copy.
for(CharacterModelNode* node in self.allPlayers)
{
// Some operation that may mutate allPlayers (ex: player dies from poison damage and is removed from this array at some other part of an app)
}
//workaround in some cases - create a copy of array and run operations that can kill player on this array (it will not be mutated anywhere else in the app
NSArray* allPlayersCopy = [self.allPlayers copy];
for(CharacterModelNode* node in allPlayersCopy)
{
[node.character refreshBuffs];
}

use try and catch for handling exception in nsmutable array
#try {
//code for accessing element.
}
#catch (NSException *exception) {
/// show exception here
}

Related

How to skip objects while iterating in a ruby like Map method for obj-c

Using the answer here this method achieves something similar to ruby's map in obj-c:
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[result addObject:block(obj, idx)];
}];
return result;
}
my question is how can i skip an object if something wrong happens while applying the block? Typically to skip something in an enumerator one just use the return command, however that's not an option in the method above, since the block is expected to return something.
In this example I use return to skip but get an error:
NSArray *mappedArray = [objArray mapObjectsUsingBlock:^(id obj, NSUInteger i) {
// i don't want this obj to be included in final array
// so I try to skip it
return; // ERROR:incompatible block pointer types sending
// 'void(^)(__strong id, NSUInteger)' to parameter of type
// 'id(^)(__strong id, NSUInteger)'
// else do some processing
return soupedUpObj;
}];
My current way of working around it is simply returning a null object, then removing them out from the final array. But I'm sure there must be a better way than that.
If the implementation is similar to what you showed above, it would make sense to just apply the block result to an intermediate value and then check it before adding it to the result array. Something like this:
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id blockResult = block( obj, idx );
if( result != nil ){
[result addObject:blockResult];
}
}];
return result;
}
One quick remedy: Write your own variant, which uses an NSPointerArray rather than NSArray. NSPointerArray can hold nil. Then order can be preserved, and you may use nil to indicate error (assuming NSNull is a valid value which cannot be used to indicate error). With the NSPointerArray, your block would just return nil.
This is simply a limitation of whatever framework or library mapObjectsUsingBlock: comes from (it's not a standard Apple API).
Implementing array map functionality is not difficult however, so you can easily write your own version which handles nil return values from the block argument.

error in array cleaning method

I am attempting to use this array cleaning method, and there seems to be an error. I can't spot it, I know the array goes in with 3116 items, comes out with 3116 (and I know for a fact there are three duplicates.
Please advice, thanks!
-(NSArray*) removeDuplicates:(NSArray*)inputArray{
NSMutableArray *arrayToClean = [NSMutableArray arrayWithArray:inputArray];
for (int i =0; i<[arrayToClean count]; i++) {
for (int j=(i+1); j < [arrayToClean count]; j++) {
if ([[arrayToClean objectAtIndex:i] isEqual:[arrayToClean
objectAtIndex:j]]) {
[arrayToClean removeObjectAtIndex:j];
j--;
}
}
}
NSArray *arrayToReturn = [NSArray arrayWithArray:arrayToClean];
return arrayToReturn;
}
NSSet will make this a lot easier:
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSSet *unique = [NSSet setWithArray:inputArray];
return [unique allObjects];
}
Please note that a set has no guaranteed order. If you need the objects in the array to be in a specific order then you should sort the resulting array as needed.
It may also be appropriate to use an NSSet instead of the original array, then you don't need to worry about duplicates at all. But this depends on the other needs of your array.
Hey You can use another alternative for this.You can use the NSSet here for this task.
NSSet declares the programmatic interface for static sets of distinct objects
You can use sets as an alternative to arrays when the order of elements isn’t important and performance in testing whether an object is contained in the set is a consideration—while arrays are ordered, testing for membership is slower than with sets.
You Just need To call below method.
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSSet *finalData = [NSSet setWithArray:inputArray];
return [finalData allObjects];
}
If really face any problem in above way of cleaning ducplicates then you can try another Alterantive.
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSMutableArray *inputArray1=[NSMutableArray arrayWithArray:inputArray];
NSMutableArray *finalARray=[[NSMutableArray alloc]init];
for (id obj in inputArray1)
{
if (![finalARray containsObject:obj])
{
[finalARray addObject: obj];
}
NSLog(#"new array is %#",finalARray);
}
return finalARray;
}
I hope it may help you ...
Here is a helper function I had in a previous project to do the exact same thing
- (NSMutableArray *)removeDuplicates:(NSMutableArray *)sortedArray{
NSMutableSet* valuesAdded = [NSMutableSet set];
NSMutableArray* filteredArray = [[NSMutableArray alloc] init];
NSString* object;
/* Iterate over the array checking if the value is a member of the set. If its not add it
* to the set and to the returning array. If the value is already a member, skip over it.
*/
for (object in sortedArray){
if (![valuesAdded member:object]){
[valuesAdded addObject:object];
[filteredArray addObject:object];
}
}
return filteredArray;
}

Printing array inside array

I have class Building in which i have one class member NSMutableArray *subnode.I have declared one more array buildingArray which stores the element of type Building.I have tried to print Building class object as shown in following code.But goes in only first for loop.
Second for loop of subnode array is not executing . Is this proper way of printing the object having the array as a one of its class member.
code:
for(Building *b in buildingArray)
{
NSLog(#"inside building array");
for(NSString *str in b.subnode)
{
NSLog(#"inside subnode array");
}
}
If this is for debugging purposes, I would recommend trying the following: Every object that inherits from NSObject inherits its description method.
Add this to Building.m:
#implementation Building
- (NSString *)description {
NSMutableString *description = [NSMutableString stringWithString:[super description]];
// add the following lines for any relevant properties
// [description appendFormat:#", materials == %#", materials];
// then have the subnode print itself:
[description appendFormat:#", subnode == %#", subnode];
return description;
}
#end
You can then print the entire buildingArray by simply calling the following code:
NSLog(#"buildingArray == %#", buildingArray);
for(Building *b in buildingArray)
{
NSLog(#"inside building array");
NSMutableArray *temp = [NSMutableArray arrayWithArray:b.subnode]
for(id *str in temp)
{
NSLog(#"inside subnode array");
}
}
this should work. happy coding :)
Your code seems to be ok. Just check if the array (subnode) is allocated and initialized. Also check if it has some values in it. I have used similar code and it works for me.
Change following code. I don't know what do you mean buy Building . So i just used id instead of Building to avoid any type of confusion.
for(id b in buildingArray)
{
NSLog(#"inside building array");
NSArray *temp = b;
for(NSString *str in temp)
{
NSLog(#"inside subnode array");
}
}
Hope, this will help you;

What is error in this code?

for(NSDictionary *feed in Feeds)
{
NSString *feedName=[feed objectForKey:#"name"];
if(listofBusiness==nil)
{
listofBusiness=[[NSMutableArray alloc]init];
}
if([listofBusiness indexOfObject:feedName] !=NSNotFound)
{
[listofBusiness addObject:feedName];
[feedName release];
feedName=nil;
}
}
in this code when compiler comes on this statement
if([listofBusiness indexOfObject:feedName] !=NSNotFound)
then not go into codition and go to increment in for loop so that any element is not added in array.what is error in this code?
The logic appears to be inverted - you probably want it to add the elemement when
[listofBusiness indexOfObject:feedName] == NSNotFound
But at the moment you have the opposite - you only try to add the object when it is 'not not found' - i.e. when it is already present in the list.
indexOfObject is not working for an array
Try with containsObject method of an array.
Example :
if([listofBusiness containsObject:feedName]) {
// your code
}

Problem with leaking NSStrings

My code leaks but I do not know exactly what am I doing wrong. Simply I have a function that takes array with NSStrings and outputs NSString formatted as CSV.
Here is my code:
-(NSString*)generateCSVfromArray: (NSMutableArray*) reportEntries {
NSString* accumulator = [NSString stringWithString:#""];
for (NSString* string in reportEntries) {
NSString* temp = [accumulator stringByAppendingString:string];
accumulator = temp;
if (![string isEqualToString:#"\n"]) {
NSString* temp = [accumulator stringByAppendingString:#";"];
accumulator = temp;
}
}
return accumulator;
}
When I check leaks in Instruments it turns out that many string objects leaked. I managed to isolate the problem to the method above. Can you please help me and point what am I doing wrong?
I don't believe you're leaking any strings in this method. Why do you think this is the method to blame? Remember that Instruments will tell you where the object was created, but this has little to do with where it is leaked. Run the Static Analyzer for more help with that (Cmd-Shift-A).
This method is wildly inefficient, though. You're creating a ton of temporary strings. You could write this much more efficiently like this:
-(NSString*)generateCSVfromArray:(NSArray*)reportEntries {
NSMutableString* accumulator = [NSMutableString string];
for (NSString* string in reportEntries) {
[accumulator appendString:string];
if (![string isEqualToString:#"\n"]) {
[accumulator appendString:#";"];
}
}
return accumulator;
}
There are of course very good CSV writers already available. Search for "Cocoa CSV." But I assume you want this specialized algorithm.