0

I have an NSMutableArray of UIImageViews which I iterate through continuously (using a CADisplayLink). I do this to continuously move the UIImageViews around the screen.

When I remove a UIImageView from the array, I sometimes get the error:

EXC_BAD_ACCESS (code=1, address=0x20000008)

However I use to get the error "Array was mutated while being enumerated" in the past before I started using ARC/iOS6 so I think these two errors mean exactly the same thing but i'm not sure.

Anyway here is my question. Removing a UIImageView from the array using any of the following methods gives me the error stated above.

[imageViews removeObject:imageView];

[imageViews performSelector:@selector(removeObject:) withObject:imageView];

[imageViews performSelectorOnMainThread:@selector(removeObject:) withObject:imageView waitUntilDone:YES];

However, using any of the methods below never gives me the error.

[imageViews performSelectorOnMainThread:@selector(removeObject:) withObject:imageView waitUntilDone:NO];

[imageViews performSelector:@selector(removeObject:) withObject:imageView afterDelay:.01];

So can someone explain to me why removing an object from an NSMutableArray using the first set of methods listed above gives me what I believe is a "Array was mutated while being enumerated" error, while using the second set of methods NEVER gives me that error?

I have been using the second set of methods listed above all the time to get around this error (and everything works perfectly if I use them) but I would like to know exactly why my problem is resolved when I use them and if it is safe to use one of those two methods.

This is my first time posting a question so forgive me if I am posting this the wrong way.

Vineet Singh
  • 4,009
  • 1
  • 28
  • 39
Epic Byte
  • 33,840
  • 12
  • 45
  • 93

2 Answers2

2

Because CADisplayLink calls back on a background thread, it's entirely possible that by scheduling the array in question to remove a given object from said thread, that another is in the process of mutating or enumerating the array, which throws the exception. The top two methods don't work because they require synchronized access to the array (which NSMutableArray doesn't provide), and they schedule in the run loop of the current thread, rather than the main thread, so their respective executions are not guaranteed to not access the same resource at the same time. The bottom two work because, assuming you're mutating the array on the main thread, it is impossible for that run loop to schedule the instruction to enumerate and the instruction to mutate concurrently (one thread can't run two pieces of code at the same time, especially when that code is scheduled in the next iteration of the run loop), so the array is much safer to access. By using a delay, you create a race conditions, which for you has worked so far, but will come back to haunt you at a later date.

CodaFi
  • 43,043
  • 8
  • 107
  • 153
  • 1
    @gfrs The overhead is so marginal, it's not worth optimizing for. As for getting the error, it's actually safer to pass YES, your tests are just revealing another race condition you have to fix. – CodaFi Mar 12 '13 at 02:52
1

When you enumerate through an array (step through it with a for … in … loop or use an NSEnumerator) the enumeration requires that you do not alter the array at all during the loop itself or on another thread at the same time (hence the original warning).

I don't know why the bottom two methods work - I would guess internally that they alter the array in a way that does not effect the enumeration.

Having said that, it's bad practice and I would not recommend altering an array during it's enumeration in any way whatsoever. Even if it works today, it may not tomorrow. What I would recommend is storing the indexes to be removed until after the enumeration, then remove them as in this answer.

Community
  • 1
  • 1
Alastair Stuart
  • 4,165
  • 3
  • 36
  • 33