NSMutableDictionary: Mutation, the Enumeration Crash, and Thread Safety
Learn NSMutableDictionary's mutation API, understand precisely why mutating a dictionary while enumerating it crashes and how to avoid it, and understand why NSMutableDictionary is not thread-safe for concurrent access.
NSMutableDictionary is to NSDictionary what NSMutableArray is to NSArray: the mutable subclass that adds the ability to insert, update, and remove key-value pairs after creation, while keeping everything NSDictionary already provides — subscripting, objectForKey:, and both forms of enumeration. It is the collection you reach for whenever a key-value store needs to change over its lifetime: a cache keyed by identifier, a set of user preferences being edited, or any running tally keyed by category. This lesson covers its mutation methods, then focuses on the two hazards that catch developers off guard most often — crashing by mutating a dictionary mid-enumeration, and assuming (wrongly) that NSMutableDictionary is safe to touch from multiple threads at once.
Setting and Updating Values
setObject:forKey: is the fundamental mutation method: [cache setObject:@"active" forKey:@"session"] inserts a new key-value pair, or, if "session" is already a key in the dictionary, replaces its existing value with the new one — there is no separate 'insert' versus 'update' method, because a dictionary key is unique by definition, so setting always does the right thing regardless of whether the key already existed.
Subscript assignment is exact sugar for the same operation: cache[@"session"] = @"active"; compiles to the identical setObject:forKey: call, and the two forms are used interchangeably in modern Objective-C, much like reading with subscripting versus objectForKey:. One meaningful difference from NSDictionary's creation restriction: assigning nil through the subscript form, cache[@"session"] = nil;, is explicitly defined to *remove* that key, exactly equivalent to calling removeObjectForKey:@"session" — this is a deliberate, documented convenience, not an accident, and it is one of the few places where nil as a right-hand-side value has defined, useful meaning rather than being disallowed.
Removing Entries
removeObjectForKey: deletes a single entry by key: [cache removeObjectForKey:@"session"]. If the key is not present, this does nothing — like NSMutableArray's removeObject:, it is safe to call speculatively without checking existence first. removeAllObjects empties the dictionary entirely in one call, which — exactly as with NSMutableArray — is both clearer and more efficient than looping over every key and removing them one at a time.
There is also removeObjectsForKeys:, which takes an array of keys and removes all of them in one call — the batch equivalent of calling removeObjectForKey: in a loop, but expressed as a single intention-revealing statement, and the natural tool to reach for once you have collected a list of keys you have decided to delete, which is exactly the pattern the next section builds toward.
Mutation During Enumeration: A Documented Crash
Both NSDictionary's fast-enumeration for-in loop and enumerateKeysAndObjectsUsingBlock: assume the dictionary's set of keys stays fixed for the duration of the enumeration. If you call setObject:forKey:, removeObjectForKey:, or any other mutating method on the same dictionary from inside a loop that is currently enumerating it, Foundation detects the inconsistency and raises an exception at runtime — typically an NSGenericException with a message containing the phrase 'was mutated while being enumerated'. This is not a rare edge case or a theoretical concern; it is one of the most common Foundation crashes in real Objective-C codebases, because the code that triggers it often looks completely reasonable at a glance.
The underlying reason is that fast enumeration works by taking a snapshot of the collection's internal state (in older, non-ARC-era terms, a mutation counter or a direct memory layout assumption) at the start of enumeration, and checking that state has not changed on each subsequent iteration. A dictionary insertion or removal can trigger the hash table to rehash or resize its internal buckets, which invalidates whatever internal position the enumerator was tracking — continuing to enumerate after that would risk skipping entries, visiting entries twice, or reading freed memory, so Foundation chooses to crash loudly and immediately rather than silently produce wrong or unsafe results.
The standard, correct fix is the 'collect first, mutate after' pattern: during enumeration, only read from the dictionary and record which keys need to be removed or changed into a separate collection (typically an NSMutableArray of keys, or an NSMutableSet); once enumeration has fully finished, perform the actual mutations — often in one batched removeObjectsForKeys: call — against the now-unobserved dictionary. This defers every mutation until no enumeration is in progress, which is exactly what Foundation requires.
• Crash: calling setObject:forKey: or removeObjectForKey: on a dictionary while a for-in loop or enumerateKeysAndObjectsUsingBlock: is currently iterating it. • Symptom: NSGenericException, message containing 'was mutated while being enumerated'. • Fix: collect the keys to change/remove into a separate array during enumeration; apply the mutation after enumeration completes.
A Worked Example of the Crash and Its Fix
Consider a cache that should drop every entry whose value is the string "expired". The instinctive first attempt is to loop over the dictionary with for (id key in cache), check each value, and call [cache removeObjectForKey:key] the moment an expired entry is found. This crashes on the very first removal, because the removal happens while the for-in loop's enumerator is still active over cache.
The corrected version separates the two passes cleanly: a first pass over the unmodified dictionary builds an NSMutableArray of the keys that should be removed, doing nothing but reading; a second pass, run only after the enumeration in the first pass has fully completed, calls removeObjectsForKeys: once with that collected list. No mutation ever happens while an enumerator is live, so there is nothing for Foundation to detect and no crash occurs.
Thread Safety
NSMutableDictionary, like essentially every mutable collection class in Foundation (NSMutableArray, NSMutableSet, NSMutableString included), provides no built-in thread safety whatsoever. Foundation's documentation is explicit on this point: it is safe for any number of threads to read from the same NSDictionary or NSMutableDictionary concurrently, as long as none of them are writing to it at the same time, but the moment one thread mutates a mutable collection while any other thread is reading from or writing to that same instance, the behavior is undefined — not merely 'you might read a stale value', but genuinely undefined, up to and including memory corruption and crashes that can be extremely difficult to reproduce and debug, because the corruption may not surface until long after the racing access that caused it.
This means code that hands the same NSMutableDictionary instance to multiple threads or dispatch queues, and lets more than one of them call setObject:forKey: or removeObjectForKey: without any coordination, has a latent bug — one that may run correctly thousands of times in testing and then crash unpredictably in production the moment two writes happen to land close enough in time. The absence of a crash during testing is not evidence of correctness here; race conditions are notoriously timing-dependent and can hide for a long time before conditions align to expose them.
This course does not teach the synchronization tools that solve this problem in this module — that is the proper subject of the later Concurrency and Thread Safety modules, which cover locks, dispatch queues, and the specific patterns (serial queue access, @synchronized blocks, and similar) used to make shared mutable state like an NSMutableDictionary safe to touch from more than one thread. What matters here is the fact itself, stated plainly: NSMutableDictionary is not thread-safe, concurrent unsynchronized access is a real and serious bug, not a theoretical one, and any code sharing one across threads needs a synchronization strategy from one of those later modules before it can be considered correct.
NSMutableDictionary Mutation Methods
setObject:forKey: and subscript assignment insert or update — a dictionary key is always unique, so there is no separate insert-only method. Assigning nil through the subscript form removes the key; NSDictionary's own @{ } literal, by contrast, cannot hold nil at all. Never call any mutating method on a dictionary while a for-in loop or enumerateKeysAndObjectsUsingBlock: is actively enumerating it.
NSMutableDictionary<NSString *, NSString *> *cache = [NSMutableDictionary dictionary];
cache[@"session"] = @"active"; // setObject:forKey: via subscript
[cache setObject:@"stale" forKey:@"lastCheck"]; // explicit method form
cache[@"lastCheck"] = nil; // removes the key (subscript-nil is defined as removal)
[cache removeObjectForKey:@"session"];
[cache removeObjectsForKeys:@[@"a", @"b"]];
[cache removeAllObjects];Code Examples
setObject:forKey: (here via subscript assignment) both inserts a brand-new key and overwrites an existing one with the same call — there is no separate update method because a key can only map to one value at a time. removeObjectForKey: deletes an entry outright, after which a lookup for that key returns nil just as it would for a key that was never present.
scores[@"Amir"] = @95; // update: overwrites the existing value for an existing key
The for-in loop holds an active enumerator over cache. Calling removeObjectForKey: on that same dictionary from inside the loop body mutates the collection mid-enumeration, which Foundation detects and reports by raising NSGenericException rather than allowing enumeration to continue over now-inconsistent internal state.
Common mistake: Removing (or adding, or replacing) entries directly inside a for-in or enumerateKeysAndObjectsUsingBlock: loop over the same dictionary always risks this crash — it depends on there being at least one mutation call reached during an active enumeration, which a real 'find and remove matches' loop like this one reaches immediately.
The first pass only reads from cache and appends matching keys to a separate NSMutableArray — no mutating method is called on cache itself while its enumerator is active. Only after enumerateKeysAndObjectsUsingBlock: has fully returned does the code call removeObjectsForKeys:, batching every removal into one call against a dictionary that is not currently being enumerated by anything.
[cache removeObjectsForKeys:keysToRemove]; // safe: enumeration has fully finished
NSMutableDictionary<NSString *, NSNumber *> *scores = [NSMutableDictionary dictionary];
scores[@"Amir"] = @92;
scores[@"Mina"] = @88;
scores[@"Amir"] = @95; // update: overwrites the existing value for an existing key
NSLog(@"%@", scores[@"Amir"]); // 95
[scores removeObjectForKey:@"Mina"];
NSLog(@"%@", scores[@"Mina"]); // nil — key removedNSMutableDictionary<NSString *, NSString *> *cache = [NSMutableDictionary dictionaryWithDictionary:@{
@"a": @"expired",
@"b": @"active",
@"c": @"expired"
}];
for (NSString *key in cache) {
if ([cache[key] isEqualToString:@"expired"]) {
[cache removeObjectForKey:key]; // CRASH on the first match
}
}
// *** Terminating app due to uncaught exception 'NSGenericException',
// reason: '*** Collection <__NSDictionaryM: ...> was mutated while being enumerated.'NSMutableDictionary<NSString *, NSString *> *cache = [NSMutableDictionary dictionaryWithDictionary:@{
@"a": @"expired",
@"b": @"active",
@"c": @"expired"
}];
NSMutableArray<NSString *> *keysToRemove = [NSMutableArray array];
[cache enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
if ([value isEqualToString:@"expired"]) {
[keysToRemove addObject:key]; // only reading + recording, no mutation yet
}
}];
[cache removeObjectsForKeys:keysToRemove]; // safe: enumeration has fully finished
NSLog(@"%@", cache); // { b = active; }Collect-First, Mutate-After
Separating the read pass from the mutate pass in time is what avoids the 'was mutated while being enumerated' crash — the two passes never overlap.
Pass 1 (read-only, enumerator active):
for key in cache -> is value "expired"? -> record key in keysToRemove
[enumeration ends]
Pass 2 (mutation, no enumerator active):
removeObjectsForKeys(keysToRemove) -- safe, cache is not being enumeratedImportant Rules
setObject:forKey: and subscript assignment both insert-or-update; there is no separate method needed for a key that may or may not already exist.
Assigning nil through the subscript form on a mutable dictionary removes that key — a documented, deliberate behavior, not an error.
Never call a mutating method on a dictionary from inside a for-in loop or enumerateKeysAndObjectsUsingBlock: block that is currently enumerating that same dictionary.
To remove or change entries based on a condition checked during enumeration, collect the affected keys into a separate array first, then mutate only after enumeration has fully finished.
NSMutableDictionary is not thread-safe: concurrent reads with no concurrent write are fine, but any concurrent write alongside another read or write on the same instance is undefined behavior and needs synchronization from the later Concurrency modules.
In-Memory Caches
A common pattern in image-heavy or network-heavy apps is an in-memory cache — often literally NSCache, but conceptually and sometimes literally an NSMutableDictionary keyed by URL or identifier — that stores recently fetched results to avoid redundant work. Two problems this lesson covers show up constantly in real cache code: a periodic 'evict expired entries' sweep that must collect keys first and remove them after, rather than mutating mid-loop, and the fact that a cache accessed from both a background download queue and the main thread needs explicit synchronization, because NSMutableDictionary itself provides none.
for (id key in dict) { if (shouldRemove(key)) { [dict removeObjectForKey:key]; } }
NSMutableArray *toRemove = [NSMutableArray array]; for (id key in dict) { if (shouldRemove(key)) { [toRemove addObject:key]; } } [dict removeObjectsForKeys:toRemove];
Mutating a dictionary while a for-in loop is actively enumerating it raises NSGenericException with a 'was mutated while being enumerated' message. Collecting the keys to remove during a read-only pass, then removing them in a second pass after enumeration ends, avoids the crash entirely.
// Multiple dispatch queues both call [sharedCache setObject:x forKey:y] on the same NSMutableDictionary with no synchronization
// Route all writes (and, typically, reads) through a serial dispatch queue or a lock, covered in the Concurrency modules
NSMutableDictionary provides no built-in thread safety. Concurrent writes from multiple threads without synchronization are undefined behavior — not just 'occasionally wrong values', but potential memory corruption and crashes that can be very hard to reproduce, since the failure may not surface at the exact moment the race occurred.
NSDictionary *frozen = @{@"a": @1}; [frozen setObject:@2 forKey:@"a"]; // frozen is NSDictionary, not NSMutableDictionary
NSMutableDictionary *editable = [@{@"a": @1} mutableCopy]; [editable setObject:@2 forKey:@"a"];
setObject:forKey: is not a method NSDictionary has at all — it is added by the NSMutableDictionary subclass. Calling it on a plain NSDictionary is a compile error (or, if accessed through an untyped id and reached at runtime, an unrecognized-selector crash); an NSDictionary intended for editing must first be turned into an NSMutableDictionary via mutableCopy or explicit construction.
What happens when you call setObject:forKey: with a key that already exists in the dictionary? A. It raises an exception because the key already exists B. It silently does nothing C. It overwrites the existing value for that key with the new one D. It creates a duplicate entry with the same key
C. It overwrites the existing value for that key with the new one — A dictionary key is unique by definition, so setObject:forKey: always does the correct thing whether the key is new or already present: for an existing key, it simply replaces the stored value.
What is the defined behavior of dict[@"key"] = nil; on an NSMutableDictionary? A. It is a compile error, exactly like NSDictionary's @{} literal B. It stores a literal nil as the value for that key C. It removes that key from the dictionary, equivalent to removeObjectForKey: D. It raises a runtime exception
C. It removes that key from the dictionary, equivalent to removeObjectForKey: — Assigning nil through subscript assignment on a mutable dictionary is documented, defined behavior for removing that key — exactly equivalent to calling removeObjectForKey:. This differs from NSDictionary's @{} literal syntax, which cannot hold nil as a value at all.
Why does mutating an NSMutableDictionary while a for-in loop is enumerating it raise an exception? A. Because NSMutableDictionary does not allow any mutation ever B. Because the enumerator relies on the collection's internal state staying consistent for the duration of enumeration, and a mutation can invalidate that state (e.g. via rehashing) C. Because for-in loops can only be used with NSArray, never NSDictionary D. It is actually a compiler warning, not a runtime exception
B. Because the enumerator relies on the collection's internal state staying consistent for the duration of enumeration, and a mutation can invalidate that state (e.g. via rehashing) — Fast enumeration assumes the collection's structure does not change mid-iteration. A mutation during enumeration can trigger internal rehashing that invalidates the enumerator's tracked position, so Foundation detects this and raises NSGenericException rather than risk skipped entries, duplicate visits, or corrupted memory access.
What is the standard fix for needing to remove entries from a dictionary based on a condition checked during enumeration? A. Call removeObjectForKey: directly inside the enumeration loop as soon as the condition is true B. Collect the keys to remove into a separate array during a read-only enumeration pass, then remove them after enumeration finishes C. Wrap the removal call in a @try/@catch block to swallow the exception D. Use a while loop instead of for-in, which does not have this restriction
B. Collect the keys to remove into a separate array during a read-only enumeration pass, then remove them after enumeration finishes — The 'collect first, mutate after' pattern defers every mutation until enumeration has fully completed, so no mutating call ever overlaps with an active enumerator — this avoids the crash entirely rather than working around its symptom.
Which statement correctly describes NSMutableDictionary's thread safety? A. It is fully thread-safe for both concurrent reads and concurrent writes with no extra work needed B. Concurrent reads from multiple threads are safe as long as no thread is writing at the same time; any concurrent write alongside another read or write is undefined behavior C. Only removeObjectForKey: is unsafe to call from multiple threads; setObject:forKey: is safe D. It is thread-safe only on iOS, not on macOS
B. Concurrent reads from multiple threads are safe as long as no thread is writing at the same time; any concurrent write alongside another read or write is undefined behavior — Foundation's mutable collections, including NSMutableDictionary, provide no built-in synchronization. Multiple concurrent readers with zero writers is safe, but any concurrent write alongside other access to the same instance is undefined behavior and requires explicit synchronization, a subject covered later in this course's Concurrency and Thread Safety modules.
Write a method -(void)incrementCountForKey:(NSString *)key inDictionary:(NSMutableDictionary<NSString *, NSNumber *> *)counts that increments the integer count stored under key, treating a missing key as a starting count of 0, without ever crashing on a missing key.
• (void)incrementCountForKey:(NSString *)key inDictionary:(NSMutableDictionary<NSString *, NSNumber *> *)counts { NSNumber *current = counts[key]; NSInteger newValue = (current == nil) ? 1 : (current.integerValue + 1); counts[key] = @(newValue); }
Look the current value up with objectForKey: or subscripting; if it comes back nil, treat that as 0 before adding 1, then store the new NSNumber back under the same key with setObject:forKey: or subscript assignment.