2

I have a NSMuableDictionary accessed by parallel threads,wherein few threads will be enumerating and few threads will be mutating.But we cant achieve this since

"Collections cannot be mutated during enumeration"

.Thought of using NSLock,but then locking the dictionary till the enumeration completes will result in slow performance.In java we have concurrent hashmap which is smart enough to take care this scenario.Is there any better idea to achieve this in iOS?.Please help.

giorashc
  • 13,691
  • 3
  • 35
  • 71
Karthik207
  • 493
  • 9
  • 27

3 Answers3

6

Read/Write access to Objective-C containers is generally not thread safe.

You can solve the concurrency problem through associating the container to a dedicated dispatch queue, and then executing ALL read and write accesses on that queue. The queue can be either serial or concurrent.

You can invoke the following methods on any dedicated thread. However, I would suggest to switch your implementation to dispatch lib eventually.

Here, we create a serial queue:

dispatch_queue_t sync_queue = dispatch_queue_create("sync_queue", NULL);

Write access can be made asynchronously:

- (void) addItem:(id)item forKey:(NSString*)key {
    dispatch_async(sync_queue, ^{
        [self.dict setObject:item forKey:key];
    });
}

Read access which returns a value must be synchronous:

- (id) itemForKey:(NSString*)key {
     __block id item;
     dispatch_sync(sync_queue, ^{
         item = [self.dict objectForKey:key];
     });
     return item;
}

You can define similar wrappers for any task:

- (void) iterate {
    dispatch_async(sync_queue, ^{
        for (id item in self.dict) {
            ...
        }
    });
}

Using a concurrent queue:

dispatch_queue_t sync_queue = dispatch_queue_create("sync_queue", DISPATCH_QUEUE_CONCURRENT);

Write access can be made asynchronously and needs a barrier:

- (void) addItem:(id)item forKey:(NSString*)key {
    dispatch_barrier_async(sync_queue, ^{
        [self.dict setObject:item forKey:key];
    });
}

Read access which returns a value must be synchronous, and it doesn't need a barrier. Basically, there's no difference for synchronous and concurrent queues:

- (id) itemForKey:(NSString*)key {
     __block id item;
     dispatch_sync(sync_queue, ^{
         item = [self.dict objectForKey:key];
     });
     return item;
}
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • Pro: this works and is the intended way in iOS. Con: it makes your access to the map asynchronous and usually winds up with lazy developers throwing everything to the main queue. – ThisCompSciGuy Apr 14 '21 at 22:18
0

There is no equivalent in objective-c, but you can avoid errors like "mutated while being enumerated" by holding elements in a temporary Array. See this answer for more info.

Community
  • 1
  • 1
Nikos M.
  • 13,685
  • 4
  • 47
  • 61
  • Can we write a custom enumerator to achieve this? – Karthik207 Nov 19 '13 at 10:02
  • Yes, -not as easy as in java though- check this: http://nshipster.com/enumerators, https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Classes/NSEnumerator_Class/Reference/Reference.html – Nikos M. Nov 19 '13 at 10:06
0

EDIT3:

This will do it: Implementing concurrent read exclusive write model with GCD

Might still be some limitations, but at least you will have more fine-grained access and concurrent reads from several threads.

END EDIT 3:

Wrap the access to the dictionary in a class, and use @synchronized(self) {} in the access methods.

EDIT2: Never mind. This will not give you what you want. Left the answer here as an antipattern.

EDIT:

Something like this

(in your object implementation)

- (void) setObjectSynchronized:(id) value forKey:(id) key {
  @synchronized(self) {
     [self.internalDictionary setObject:value forKey:key];
  }
}


- (void) objectForKeySynchronized:(id) key {
  @synchronized(self) {
     return [self.internalDictionary objectForKey:key];
  }
}
Community
  • 1
  • 1
Anders Johansen
  • 10,165
  • 7
  • 35
  • 52
  • Done. Left out the header and the declaration and initialization of the internal NSMutableDictionary. Not near my Mac right now... – Anders Johansen Nov 19 '13 at 11:24
  • ok fine,while enumerating the NSMutableDictionary,If we call the above two methods which you mentioned,"Collections cannot be mutated" exception will be thrown right?.can the above mentioned methods be called while enumerating the dictionary? – Karthik207 Nov 19 '13 at 11:29
  • Not if you use an accessor method like the two I showed above to access the actual NSMutableDictionary. Look up @synchrnoized. It works just like synchronized in Java does. And yes, that's the same as using a lock. If you want concurrent read and write, then you need to go with the work queue example, and even here you will need to ensure exclusive write to the underlying dictionary object. – Anders Johansen Nov 19 '13 at 11:32
  • No sorry, not at the same time. Did you edit the question while I answered, or did I just miss that requirement when I read it? – Anders Johansen Nov 19 '13 at 11:36
  • See my EDIT3 answer for a link to your solution. – Anders Johansen Nov 19 '13 at 11:37