Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x138cb4d0> was mutated while being enumerated - objective-c

i am trying to remove duplicate objects from array.
NSMutableArray* filterResults = [[NSMutableArray alloc] init];
BOOL copy;
// remove duplicate
if (![arrSelectedVehicle count] == 0)
{
for (Vehicles *a1 in arrSelectedVehicle) {
copy = YES;
for (Vehicles *a2 in filterResults) {
if ([a1.Vehicle_id isEqualToString:a2.Vehicle_id]) {
copy = NO;
[arrSelectedVehicle removeObjectIdenticalTo:a2];
break;
}
}
if (copy) {
[filterResults addObject:a1];
}
}
}
i am adding two object which is already their in the array

you cannot modify an array when you are enumerating it. you can do the following:
NSMutableArray* filterResults = [[NSMutableArray alloc] init];
BOOL copy;
// remove duplicate
if (![arrSelectedVehicle count] == 0)
{
NSArray* arraycopy = [arrSelectedVehicle copy];
for (Vehicles *a1 in arraycopy) {
copy = YES;
for (Vehicles *a2 in filterResults) {
if ([a1.Vehicle_id isEqualToString:a2.Vehicle_id]) {
copy = NO;
[arrSelectedVehicle removeObjectIdenticalTo:a2];
break;
}
}
if (copy) {
[filterResults addObject:a1];
}
}
[arraycopy release];
}

You can't modify an array while using it with fast enumeration. That's what the error is telling you. You need to change the loops
for (NSUInteger i = 0; i < arrSelectedVehicle.count; i++) {
Vehicles *a1 = arrSelectedVehicle[i];
copy = YES;
for (NSUInteger j = 0; j < filterResults.count; j++) {
Vehicles *a2 = filterResults[j];
if ([a1.Vehicle_id isEqualToString:a2.Vehicle_id]) {
copy = NO;
[arrSelectedVehicle removeObjectIdenticalTo:a2];
break;
}
}
if (copy) {
[filterResults addObject:a1];
}
}

There are several problems with your code. Anyway, the easiest way to remove duplicates, if you don't care about the order of the elements, is using an NSSet, because an NSSet doesn't allow duplicates:
NSArray *uniqueObjects = [[NSSet setWithArray:arrSelectedVehicle] allObjects];

The error message says it pretty much: you can't modify the contents of a mutable collection while you're using fast enumeration on it (because that's erroneous). You have to make a mutable copy of it, and remove the duplicates from that copy.

you can not use a for loop and then add or remove objects from the array that you are iterating through (arrSelectedVehicle). Instead try building up a new array with the objects that are OK. At the end of the loop you could assign that array back to arrSelectedVehicle.

Related

How to compare two dynamic array in without using loop objective c [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I am new in iOS and I am facing a problem regarding to compare two array. I tried this code
if ([arr1 isEqualToArray:arr2])
{
NSLog(#"Print the output to update...");
}
But this not work for me.Because my array is like this
arr1=[#"1",#"2",#"3",#"4",#"5",#"6"];
arr2=[#"2"];
So, I tried a code like this
NSSet *set1 = [NSSet setWithArray:arr1];
NSSet *set2 = [NSSet setWithArray:arr2];
if ([set1 isEqualToSet:set2]) {
// equal
}
But, this not work for me.In my case arr1 is from web service and arr2 is from core data.can you suggest any other suggestion to compare this two array.
In if condition I am updating my code and in else condition in am inserting
if([arr1 isEqualToArray:arr2])
{
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSManagedObject* favoritsGrabbed = [results objectAtIndex:0];
[favoritsGrabbed setValue:#"1" forKey:#"Key"];
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
else
{
if (self.device) {
// Update existing device
[device setValue:Audit forKey:#"key1"];
[device setValue:MethodID forKey:#"key2"];
[device setValue:CheckPointID forKey:#"key3"];
[device setValue:GlobalStringChk forKey:#"key4"];
[device setValue:RegionID forKey:#"key5"];
[device setValue:BranchID forKey:#"key6"];
[device setValue:SiteID forKey:#"key7"];
[device setValue:AuID forKey:#"key8"];
[device setValue:userid forKey:#"key9"];
[device setValue:StringIndex forKey:#"key10"];
} else {
// Create a new device
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"EntityName" inManagedObjectContext:context];
[newDevice setValue:Audit forKey:#"key1"];
[newDevice setValue:MethodID forKey:#"key2"];
[newDevice setValue:CheckPointID forKey:#"key3"];
[newDevice setValue:GlobalStringChk forKey:#"key4"];
[newDevice setValue:RegionID forKey:#"key5"];
[newDevice setValue:BranchID forKey:#"key6"];
[newDevice setValue:SiteID forKey:#"key7"];
[newDevice setValue:AuID forKey:#"key8"];
[newDevice setValue:userid forKey:#"key9"];
[newDevice setValue:StringIndex forKey:#"key10"];
}
}
Hear,I need to compare array so that I can update the value in core data and if array are not equal then I need to insert them.So, I can not use loop.Please see its else condition if I used loop it insert data until loop runs and I want to insert one value at click.So, I can not use loop.
if you are trying to figure if elements from one array is present in another, you can try the following
NSArray * firstArray = #[#"1",#"2",#"3",#"4"];
NSArray * secondArray = #[#"2",#"10",#"20"];
for(int index = 0 ; index < [firstArray count] ; index++)
{
NSString * element = [firstArray objectAtIndex:index];
if([secondArray containsObject:element])
{
//do stuff
//This block will execute only when element == #"2"
}
}
for (int i = 0; i < array2.count; i++)
{
if ([array1 containsObject:[array2 objectAtIndex:i]])
{
NSLog(#"Array1 contains array2 object");
}
else
{
NSLog(#"Array1 do not contains array2 object");
}
}
You can try this. This is working for me with your above provided data.
Here are many ways.
Use nested loops to know the exact position of equal elements:
for(int i= 0; i<arr2.count; i++){
for(int j= 0; j<arr1.count; j++){
if(arr1[j] == arr2[i])
NSLog(#"index position: %d element %#:", j, array1[j]);
}
}
Or you can use containsObject: methods:
containsObject: Returns a Boolean value that indicates whether a given object is present in the array.
for(int i= 0; i<arr2.count; i++){
if ([arr1 containsObject:[arr2 objectAtIndex:i]]) {
// indicates whether a given object is present in the array.
}
}
From your comments and title I guess:
By "compare" and "is equal" you are meaning to test membership; and
Your second array only contains one item (so there is no need to loop)
If this is correct then change your line:
if([arr1 isEqualToArray:arr2])
to
if([arr1 containsObject:arr2[0]])
which checks whether arr1 contains the first element of arr2 (i.e. arr2[0]).
HTH
Try this
arr1=[#"1",#"2",#"3",#"4",#"5",#"6"];
arr2=[#"2"];
//compare result
BOOL bol = (arr1.count == arr2.count);
// if count equal
if (bol) {
for (NSInteger i = 0; i < arr1.count; i++) {
NSString *str1 = [arr1 objectAtIndex:i];
NSString *str2 = [arr2 objectAtIndex:i];
if (![str1 isEqualToString:str2]) {
bol = NO;
break;
}
}
}
or use block
NSArray *arr1=#[#"1",#"2",#"3",#"4",#"5",#"6"];
NSArray *arr2=#[#"2"];
//compare result
__block BOOL bol = (arr1.count == arr2.count);
// if count equal
if (bol) {
[arr1 sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
if (![obj1 isEqualToString:obj2]) {
bol = NO;
}
return NSOrderedAscending;
}];
}

NSMutableArray subclass init

I'm trying to make a function for a NSMutableArray subclass that only uses integer, but I don't want to use "count." How do I do this?
-(NSMutableArrayWithIntegers*)initWithCount:(NSInteger)count numbers:(NSInteger)firstInt, ...
{
self = [super init];
if (self) {
va_list args;
va_start(args, firstInt);
NSInteger arg = firstInt;
for (int i = 0; i < count; i++)
{
arg = va_arg(args, NSInteger);
[self addObject: [NSNumber numberWithInteger:arg]];
}
va_end(args);
}
return self;
}
I know this doesn't answer your question but it's important to let you know. Don't ever subclass NSMutableAnything. Use a category and thank me later:
#interface NSMutableArray (ListOfIntegers)
+(NSMutableArray)mutableArrayWithIntegers:(NSInteger)i, ... {
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:whatever];
// do your thing
return array;
}
#end
First of all, the approach you currently have is just fine. Don't try getting rid of the count. There are alternatives, but they are only worse.
For example, you may use a sentinel value (which may not be inserted into the array) as the last argument, but in this case, you will have to make sure that you are not actually trying to insert this value to the array at all:
- (id)initWithIntegers:(NSInteger)first, ...
{
if (!(self = [super init])) return nil;
va_list args;
va_start(args, first);
NSInteger n;
if (first != NSIntegerMax) {
[self addObject:#(first)];
while ((n = va_arg(args, NSInteger)) != NSIntegerMax) {
[self addObject:#(n)];
}
}
va_end(args);
return self;
}
But really, this unnecessarily narrows the range of values that can be added - using that count argument is not a big deal.

What is the most efficient way to generate a sequence of NSNumbers?

It's a fairly simple builtin in python for example: x = range(0,100)
How can I accomplish the same feat using objective-c methods? Surely there is something better than a NSMutableArray and a for-loop:
NSMutableArray *x = [NSMutableArray arrayWithCapacity:100];
for(int n=0; n<100; n++) {
[x addObject:[NSNumber numberWithInt:n]];
}
Yes, I am aware that doing this is most likely not what I actually want to do (ex: xrange in python), but humor my curiosity please. =)
Clarification: I would like a NSArray containing a sequence of NSNumbers, so that the array could be further processed for example by shuffling elements or sorting by an external metric.
If you want such an array, you might want to do your own specific subclass of NSArray.
A very basic implementation example would look like:
#interface MyRangeArray : NSArray
{
#private
NSRange myRange;
}
+ (id)arrayWithRange:(NSRange)aRange;
- (id)initWithRange:(NSRange)aRange;
#end
#implementation MyRangeArray
+ (id)arrayWithRange:(NSRange)aRange
{
return [[[self alloc] initWithRange:aRange] autorelease];
}
- (id)initWithRange:(NSRange)aRange
{
self = [super init];
if (self) {
// TODO: verify aRange limits here
myRange = aRange;
}
return self;
}
- (NSUInteger)count
{
return myRange.length;
}
- (id)objectAtIndex:(NSUInteger)index
{
// TODO: add range check here
return [NSNumber numberWithInteger:(range.location + index)];
}
#end
After that, you can override some other NSArray methods to make your class more efficient.
NSRange range = NSMakeRange(0, 100);
You can iterate this range by:
NSUInteger loc;
for(loc = range.location; loc < range.length; loc++)
{
}

CALayerArray was mutated while being enumerated

I have to delete an object of an array every now and then, when I do it I get this error.
Collection < CALayerArray: 0xc4f3b20> was mutated while being enumerated
The error appears on this method, which is the accesor of the Array:
- (NSArray *)occupantsArray
{
if (dispatch_get_current_queue() == moduleQueue)
{
return occupantsArray;
}
else
{
__block NSArray *result;
dispatch_sync(moduleQueue, ^{ //ON THIS LINE
result = [occupantsArray copy];
});
return [result autorelease];
}
}
As you can see Im taking care of not returning the original array but a copy, but it still crashes.
Also the method where Im deleting elements of the array is this.
- (void)eraseJIDFromArray:(NSString*)jid{
dispatch_block_t block = ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i = 0;
for(NSString *jidAct in occupantsArray){
if([jidAct isEqualToString:jid]){
[occupantsArray removeObjectAtIndex:i];
}
i++;
}
[pool drain];
};
if (dispatch_get_current_queue() == moduleQueue)
block();
else
dispatch_async(moduleQueue, block);
}
The array can have upto 200 elements, so it can take some time to go through all of them, but I'm setting queues, dont know what else I can do.
Any ideas?
Thanks.
This code:
for(NSString *jidAct in occupantsArray){
if([jidAct isEqualToString:jid]){
[occupantsArray removeObjectAtIndex:i];
}
i++;
}
is probably causing your problems. You shouldn't be removing elements while enumerating the array. The way you avoid this is by using an NSMutableIndexSet:
NSMutableIndexSet *indexes = [NSMutableIndexSet set]; // assuming NSInteger i;
for(NSString *jidAct in occupantsArray){
if([jidAct isEqualToString:jid]){
[indexes addIndex:i];
}
i++;
}
[occupantsArray removeObjectsAtIndexes:indexes];

How can I remove all NSTableColumns from an NSTableView?

I am trying to implement a method to clear the NSTableView of all items AND columns. But I get a crash when I try to implement the following:
- (void)clearResultData
{
[resultArray removeAllObjects];
NSArray *tableCols = [resultTableView tableColumns];
if ([tableCols count] > 0)
{
id object;
NSEnumerator *e = [tableCols objectEnumerator];
while (object = [e nextObject])
{
NSTableColumn *col = (NSTableColumn*)object;
[resultTableView removeTableColumn:col];
}
}
[resultTableView reloadData];
}
Well, if it's any help you can remove all the columns like this:
- (void)removeAllColumns
{
while([[tableView tableColumns] count] > 0) {
[tableView removeTableColumn:[[tableView tableColumns] lastObject]];
}
}
The NSArray returned by tableColumns is changed by removeTableColumn. Do not assume it is unchanged.
Although it is returned as a non-mutable NSArray, the underlying implementation is being modified and it is not safe to use NSEnumerator with collections that are modified. In the while loop, you are sending a nextObject message to an enumerator whose current object was just deleted -- so bad things can happen!
Here's a more efficient implementation:
NSTableColumn* col;
while ((col = [[tableView tableColumns] lastObject])) {
[tableView removeTableColumn:col];
}
When there are no columns in the table view: tableColumns returns an empty array, lastObject on an empty array returns nil, col is assigned the value of nil, the condition is false and the while loop finishes.
[[[_tableView tableColumns] copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[_tableView removeTableColumn:obj];
}];
Here is a Swift implementation:
tableView.tableColumns.forEach({tableView.removeTableColumn($0)})