Using GCD and Core Data causes crash - objective-c

I retrieve data back from our server and I need to process it.
For each key, I create a NSManagedObject. Each object is created in the same context. I am using Magical Record.
-(id)init {
if (self = [super init]){
self.context = [NSManagedObjectContext MR_contextForCurrentThread];
}
return self;
}
Threading:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
for (id key in boundariesDictionary) {
dispatch_group_async(group, queue, ^{
DLog(#"nsthread: %#", [NSThread currentThread]);
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
DLog(#"boundaryIDString: %#", boundaryIDString)
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"boundaryID == %# AND api == %#", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:self.context];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
DLog(#"creating boundary %#", boundaryIDString);
boundary = [Boundary MR_createInContext:self.context];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
}
}
}
[self processBoundary] just takes the dictionary and sets it to the managed object's attributes.
if ([boundaryDictionary objectForKey:#"name"]) {
boundary.name = [boundaryDictionary objectForKey:#"name"];
}
//more data processing
This is causing an error though:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x1776f7f0> was mutated while being enumerated.'
It runs fine if I don't use the same context for each thread.
I don't understand what set other then the NSDictionary boundariesDictionary that i'm enumerating through. I am not mutating boundariesDictionary at all, only copying the data into core data.
When I PO the object (0x1776f7f0 in this case), I get a list of Boundary objects in a set. Those Boundary objects would only exist in the NSManagedObjectContext "set", I don't add them to an NSArray, NSDictionary, or NSSet. But I don't believe I enumerate over that set. I do mutate it by creating new boundary objects to it.
I think there is something going on that I don't understand or quite grasp yet.
Any ideas?
UPDATE:
for (id key in boundariesDictionary) {
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DLog(#"saveWithBlock thread: %#", [NSThread currentThread]);
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"boundaryID == %# AND api == %#", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:localContext];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
boundary = [Boundary MR_createInContext:localContext];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
if (boundary == nil) {
//Prompt Error
}
else {
for (NSNumber *groupID in groupIDs) {
if ([groupID isKindOfClass:[NSNumber class]]) {
Group *group = [Group MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"groupID == %# OR groupID == 0 AND api == %#", groupID, self.serverCall.API]];
if (group != nil) {
group.lastUpdated = [NSDate date];
[group addBoundariesObject:boundary];
}
else {
DLog(#"group %# DNE", groupID);
}
}
}
}
}
} completion:^(BOOL success, NSError *error) {
DLog(#"saveWithBlock completion Block | time: %f", [[NSDate date] timeIntervalSinceDate:startTime]);
}];
}
}
So for my Group 29 should see all the boundaries I'm creating, but its not. Its inconsistent. Sometimes sees all, sometimes some, and sometimes none.
Also, I often see
NO CHANGES IN ** BACKGROUND SAVING (ROOT) ** CONTEXT - NOT SAVING
in the log. Its also inconsistent on how many of these messages I see, while the context that do save will insert more then 1 object.
Not sure if that is how it should be behaving, seems like it should be a 1-to-1 ratio if each block has its own context and each block only creates 1 object.
I log each thread ID, and it is creating a new thread for each block. No thread ID is being logged twice, so the threads shouldn't be being reused.

Managed object contexts are not thread safe, you must not use them or any managed objects/sets/data structures/whatever they might return from different queues or threads.
I don't know if magical record supports it, but you should definitely be using queue containment for your contexts, and then you have to do everything within the context of the MOC's private queue.
If you use thread containment, you must guarantee that the context and any managed objects created by the context are always serially accessed, which you're absolutely not doing in the code above.

You're issue is that gcd queues a threads are not a one to one mapping. GCD reuses threads, and thus your are likely crossing thread boundaries unknowingly here. My suggestion is to simply create a new context and stop using contextForCurrentThread. I wrote more details about the issues on my blog. ContextForCurrentThread will be deleted in an upcoming release.

Related

Nesting methods with completion blocks

I have several methods that have the following structure:
- (void) doSomethingWithCompletion: (void (^)(NSError *error)) completion {
__block NSError *fetchError = nil;
dispatch_group_t dispatchGroup = dispatch_group_create();
for (Item* item in self.items)
{
dispatch_group_enter(dispatchGroup);
// fetchError = fetch online data
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(),^{
if (completion)
completion(fetchError);
});
}
My goal is to run several doSomethings after each other, so I could so something like this:
[self doSomethingAWithCompletion: ^(NSArray *results NSError *error) {
if (error == nil) {
[self doSomethingBWithArray: results withCompletion: ^(NSError *error) {
if (error == nil) {
[self doSomethingCWithCompletion: ^(NSError *error) {
if (error == nil) {
// done!!
}
}];
}];
}];
What I am struggling with is the second code block (no pun); is nesting all the methods the way to go, or are there other solutions?
The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
Also, doSomethingBWithCompletion uses data that is generated in doSomethingAWithCompletion, etc.
EDIT: After a lot of thinking, refactoring, and simplifying my code, I was able to end up with only two functions, using the nested approach as I outlined above and with a #property for the results array.
The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
According to the comments:
The Results of the block are not depending on the result of the first aren't they?
And
Yes they are. For instance, in the first doSomething I determine which items are outdated, in the second doSomething I download and parse the updated items, and in the third doSomething I save them to the store.
(BTW: You should really add this information to your Q.)
If an action depends on the result (not only execution) of a previous action, you have to nest the blocks. Your code does not look like this, because there is no data passed to the completion blocks.
If you do not have such a dependency, you could use a private serial dispatch queue. However, this is a solution in your case, too, if you have akin of a manager class holding the data passed from block to block. But this seems to be highly anticonceptual.
There may be community attempt to add promises to objective-c, and it would be nice to have, because that's just what's needed here. Without committing to a whole new library, you can handle the nesting (which I agree is a bummer) by doing the async tasks recursively... something like this for your example code:
Start with an operation that takes no params and results in an array...
- (void)firstOpWithCompletion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSArray *components = [#"this is an array of strings from the FIRST op" componentsSeparatedByString:#" "];
if (completion) {
completion(components, nil);
}
});
}
Here are a couple that take an array param and result in an array...
- (void)secondOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the SECOND op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
completion(result, nil);
}
}
});
}
- (void)thirdOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the THIRD op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
NSLog(#"we did it. returning %#", result);
completion(result, nil);
}
}
});
}
// ...as many as these as you need
Now, as in my answer prior to this edit, we just add a param pass initially and in the intermediate calls...
- (void)doSeveralThingsInSequence:(NSArray *)todo param:(NSArray *)param {
if (todo.count == 0) return;
// you could generalize further here, by passing a "final" block and run that before the return
NSString *nextTodo = todo[0];
SEL sel = NSSelectorFromString(nextTodo);
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL, NSArray *, void (^)(NSArray *, NSError *)) = (void *)imp;
func(self, sel, param, ^(NSArray *result, NSError *error) {
if (!error) {
NSArray *remainingTodo = [todo subarrayWithRange:NSMakeRange(1, todo.count-1)];
[self doSeveralThingsInSequence:remainingTodo param:result];
}
});
}
Stepping through the code: this method bails if there's nothing to do, otherwise it takes the next selector name from the passed array, gets the C function implementation for it and invokes it, placing a completion block on the call stack that starts the process over for the remaining selectors.
Finally, doEverything calls the first operation to get started, then starts running a list of operations (which can be an arbitrarily long list) passing the array output from one as the array input to the next. (You could generalize this further by passing id's along the chain
- (void)doEverything {
[self firstOpWithCompletion:^(NSArray *array, NSError *error) {
NSArray *todo = #[ #"secondOpWithParam:completion:", #"thirdOpWithParam:completion:" ];
[self doSeveralThingsInSequence:todo param:array];
}];
}
I tested this exactly as posted and saw the expected output:
(
this,
is,
an,
array,
of,
strings,
from,
the,
FIRST,
op,
these,
strings,
are,
from,
the,
SECOND,
op,
these,
strings,
are,
from,
the,
THIRD,
op
)

Is this always true, that if managedobjectcontext change then something must be deleted, updated, or added?

NSUInteger numberOfChanges = moc.insertedObjects.count + moc.deletedObjects.count+moc.updatedObjects.count;
if (numberOfChanges ==0 )
{
NSAssert([moc hasChanges]==false,#"[moc hasChanges]==false");
return;
}
Somehow the assertion fail. I wonder why. So nothing is inserted. Nothing is deleted. Nothing is updated. Yet [moc hasChanges] is true? This happens very rarely. However, it should not have happened at all.
This is the full code if people want to see.
+(void)commit {
[BGHPTools breakIfLock];
NSManagedObjectContext *moc = [self managedObjectContext];
NSArray * arUpdatedObjects = moc.updatedObjects.allObjects;
NSArray * arUpdatedObjectsID = [arUpdatedObjects convertByPeformingSelector:#selector(objectID)];
NSUInteger numberOfChanges = moc.insertedObjects.count + moc.deletedObjects.count+moc.updatedObjects.count;
if (numberOfChanges ==0 )
{
//NSAssert([moc hasChanges]==false,#"[moc hasChanges]==false");
return;
}
if (arUpdatedObjectsID.count) {
while (false);
}
[BGFetchClass vAddObjectIDCachesForArray:moc.insertedObjects.allObjects];
[BGFetchClass vDeleteObjectsForArray:moc.deletedObjects.allObjects];
/*if (numberOfChanges ==0 )
{
NSAssert([moc hasChanges]==false,#"[moc hasChanges]==false");
return;
}*/
//NSAssert([moc hasChanges],#"[moc hasChanges]==true");
__block NSError *error;
__block BOOL saveSuccesfully;
[moc performBlockAndWait:^{
#synchronized([BGFetchClass class])
{
saveSuccesfully = [moc save:&error];
if (!saveSuccesfully) {
CLog(#"Error in Saving %#", error);
}
else{
}
}
}];
if (![NSThread isMainThread]) {
if (arUpdatedObjectsID.count) { //When we're adding new objects, this won't be called. That is the only time commit is called while we are synching
[BGHPTools vDoForeGroundAndWait:^{
NSManagedObjectContext * moc =[BGMDCRManagedObjectContextThreadHandler managedObjectContext];
for (NSManagedObjectID * moi in arUpdatedObjectsID) {
NSManagedObject * mo = [moc existingObjectWithID:moi error:nil];
NSAssert(mo!=nil, #"Object can't possibly be nil");
[mo turnToFault];
}
}];
}
}
NSManagedObjectContext * parentMoc = [self managedObjectContextMainContext]; //Main parent is not nsmainqueueconcurency type. Hence, this is save
[parentMoc performBlockAndWait:^{
if (![parentMoc save:&error])
{
CLog(#"Error in Saving %#", error);// handle error
}
}];
NSAssert(error==nil, #"Error must be nill");
}
There is one documented situation that can give you this behavior.
From NSManagedObjectContext documentation:
NSManagedObjectContext deletedObjects
Discussion
The returned set does not necessarily include all the objects that have been deleted (using deleteObject:)—if an object has been inserted and deleted without an intervening save operation, it is not included in the set.
Could it be your case?
NSManagedObjectContext.hasChanges turns YES even if you do this:
managedObject.someAttribute = managedObject.someAttribute;
NSManagedObject sometimes fails. try using:
- (BOOL)hasAnythingChanged {
return [[[self changedValues] allKeys] count] > 0;
}
(self being your NSManagedObject)
I found out that changing a property and then changing it back to its last saved value would leave the NSManagedObject dirty. Either that was the intended behavior or I did something wrong along the way. I ended up checking on the [changedValues count] which worked for me but you would have to think about checking for any transient properties yourself since they won't appear in 'changedValues' and will make your NSManagedObject dirty.

Parsing asynchronously using NSOperationQueue or GCD

I've got this parsing operation that currently works fine, but I've started to notice that it is freezing up my UI slightly so I'm trying to refactor and get this done asynchronously. I'm having some issues however and was hoping someone could point me in the right direction. Here's my current (synchronous) code:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
return nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
return nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
return [NSArray arrayWithArray:formattedEvents];
}
I've been looking into NSOperationQueue's and I'm struggling to find a solution as I'd like to return an array from this method and operation queues are not meant to have return values. I'm also looking at GCD and i've got somethinbg like this:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSMutableArray *mutable = [NSMutableArray array];
dispatch_async(backgroundQueue, ^{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
mutable = nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
mutable = nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
mutable = [NSMutableArray arrayWithArray:formattedEvents];
});
return [mutable copy];
}
For some reason, this seems to be returning the object before the parsing has finished however, as I'm gettting no data out of that mutable object, but I'm noticing that the parsing is indeed occurring (i'm logging out the results). can anyone give me an idea about how to get this asynch stuff going?
Thanks!!
You primary problem is that by their very nature asynchronous operations can't synchronously return a result. Instead of returning an array from -eventsFromJSON:, you should provide a way for the caller to receive a callback when the results are finished. There are two common approaches to this in Cocoa.
You can create a delegate with an associated delegate protocol including a method like -parser:(Parser *)parser didFinishParsingEvents:(NSArray *)events, then have your parser call this method on its delegate when parsing is finished.
Another solution is to allow the caller to provide a completion block to be executed when parsing is complete. So, you might do something like this:
- (void)eventsFromJSON:(NSString *)objectNotation completionHandler:(void (^)(NSArray *events))completionHandler)
{
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
NSMutableArray *mutable = [NSMutableArray array];
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
// Snip...
mutable = [NSMutableArray arrayWithArray:formattedEvents];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler([mutable copy]);
});
});
}
Then you can call this code some thing like this:
- (void)parseJSONAndUpdateUI // Or whatever you're doing
{
NSString *jsonString = ...;
Parser *parser = [[Parser alloc] init];
[parser parseEventsFromJSON:jsonString completionHandler:^(NSArray *events){
// Update UI with parsed events here
}];
}
I like the second, block-based approach better. It makes for less code in most cases. The code also reads closer to the synchronous approach where the method just returns an array, since the code that uses the resultant array simply follows the method call (albeit indented since it's in the completion block's scope).
I would recommend using a completion block that you pass into your parse method. This way you don't have to return a value, but can do what you need to with the information once it is parsed. You just have to make sure you use GCD again to put the completion block on the main thread.
You could also post a notification on the main thread once the operation is complete that contains the array in userInfo.
Returning a value will not work however for asynchronous operations.
You are getting a returned object before the parsing has finished because your return [mutable copy] is outside of the dispatch_async block. Since dispatch_async functions asynchronously, it will return immediately, and then calls your return [mutable copy] (which is empty because it's not done parsing).

How does one eliminate Objective-C #try #catch blocks like this?

I'm a developer from Python world used to using exceptions. I found in many places that using exceptions is not so wise here, and did my best to convert to NSErrors when needed. but then I encounter this:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
// Memory management code omitted
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
with some of the objects in dict containing NSNull, which would result an "unrecognized selector" exception. In that case, I want to drop that datum completely. My first instinct is to wrap the whole content of the for block into a #try-#catch block:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
#try
{
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
#catch(NSException *exception)
{
// Do something
}
}
But is this a good approach? I can't come up with a solution without repeating checks on each variable, which is really ugly IMO. Hopefully there are alternatives to this that haven't occur to me. Thanks in advance.
The proper Objective-C way to do this would be:
for (NSDictionary *dict in dicts)
{
if (! [dict isKindOfClass:[NSDictionary class]])
continue;
// ...
}
Testing if a receiver can respond to a message before sending it is a typical pattern in Objective-C.
Also, take note that exceptions in Objective-C are always a programmer error and are not used for normal execution flow.
Many people use a category on NSDictionary for these cases:
- (id)safeObjectForKey:(id)aKey
{
id obj = [self objectForKey:aKey];
if ([obj isKindOfClass:[NSNull class]])
{
return nil;
}
return obj;
}
You still need to make sure, that your dict is an actual dictionary instance.
In the end I decided to solve the problem using KVC. Something like this:
- (id)initWithPropertyDictionary:(NSDictionary *)dict
lookUpTable:(NSDictionary *)keyToProperty
{
self = [self init];
for (NSString *key in dict)
{
NSString *propertyName;
if ([keyToProperty objectForKey:key])
propertyName = [keyToProperty objectForKey:key];
else
propertyName = key;
if ([[dict objectForKey:key] isKindOfClass:[NSNull class]])
{
[self release];
return nil;
}
else
{
[self setValue:[dict objectForKey:key] forKey:propertyName];
}
}
}
The setback of this resolution is that I'll have to use NSNumber for my properties, but for JSON data there is really no distinction between floating numbers and integers, so this is fine.
And if you really want primitive types, you can couple this method with custom setters that converts those NSNumbers into appropriate types.
With this, all you need to do is check for nil before adding the object into the array. Much cleaner everywhere except the model class.
Thanks to jaydee3 for inspiring me to focus on changing the model class.

NSMutableDictionary thread safety

I have a question on thread safety while using NSMutableDictionary.
The main thread is reading data from NSMutableDictionary where:
key is NSString
value is UIImage
An asynchronous thread is writing data to above dictionary (using NSOperationQueue)
How do I make the above dictionary thread safe?
Should I make the NSMutableDictionary property atomic? Or do I need to make any additional changes?
#property(retain) NSMutableDictionary *dicNamesWithPhotos;
NSMutableDictionary isn't designed to be thread-safe data structure, and simply marking the property as atomic, doesn't ensure that the underlying data operations are actually performed atomically (in a safe manner).
To ensure that each operation is done in a safe manner, you would need to guard each operation on the dictionary with a lock:
// in initialization
self.dictionary = [[NSMutableDictionary alloc] init];
// create a lock object for the dictionary
self.dictionary_lock = [[NSLock alloc] init];
// at every access or modification:
[object.dictionary_lock lock];
[object.dictionary setObject:image forKey:name];
[object.dictionary_lock unlock];
You should consider rolling your own NSDictionary that simply delegates calls to NSMutableDictionary while holding a lock:
#interface SafeMutableDictionary : NSMutableDictionary
{
NSLock *lock;
NSMutableDictionary *underlyingDictionary;
}
#end
#implementation SafeMutableDictionary
- (id)init
{
if (self = [super init]) {
lock = [[NSLock alloc] init];
underlyingDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) dealloc
{
[lock_ release];
[underlyingDictionary release];
[super dealloc];
}
// forward all the calls with the lock held
- (retval_t) forward: (SEL) sel : (arglist_t) args
{
[lock lock];
#try {
return [underlyingDictionary performv:sel : args];
}
#finally {
[lock unlock];
}
}
#end
Please note that because each operation requires waiting for the lock and holding it, it's not quite scalable, but it might be good enough in your case.
If you want to use a proper threaded library, you can use TransactionKit library as they have TKMutableDictionary which is a multi-threaded safe library. I personally haven't used it, and it seems that it's a work in progress library, but you might want to give it a try.
Nowadays you'd probably go for #synchronized(object) instead.
...
#synchronized(dictionary) {
[dictionary setObject:image forKey:name];
}
...
#synchronized(dictionary) {
[dictionary objectForKey:key];
}
...
#synchronized(dictionary) {
[dictionary removeObjectForKey:key];
}
No need for the NSLock object any more
after a little bit of research I want to share with you this article :
Using collection classes safely with multithreaded applications
http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html
It looks like notnoop's answer may not be a solution after all. From threading perspective it is ok, but there are some critical subtleties. I will not post here a solution but I guess that there is a good one in this article.
I have two options to using nsmutabledictionary.
One is:
NSLock* lock = [[NSLock alloc] init];
[lock lock];
[object.dictionary setObject:image forKey:name];
[lock unlock];
Two is:
//Let's assume var image, name are setup properly
dispatch_async(dispatch_get_main_queue(),
^{
[object.dictionary setObject:image forKey:name];
});
I dont know why some people want to overwrite setting and getting of mutabledictionary.
Even the answer is correct, there is an elegant and different solution:
- (id)init {
self = [super init];
if (self != nil) {
NSString *label = [NSString stringWithFormat:#"%#.isolation.%p", [self class], self];
self.isolationQueue = dispatch_queue_create([label UTF8String], NULL);
label = [NSString stringWithFormat:#"%#.work.%p", [self class], self];
self.workQueue = dispatch_queue_create([label UTF8String], NULL);
}
return self;
}
//Setter, write into NSMutableDictionary
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = #(count);
}
});
}
//Getter, read from NSMutableDictionary
- (NSUInteger)countForKey:(NSString *)key {
__block NSUInteger count;
dispatch_sync(self.isolationQueue, ^(){
NSNumber *n = self.counts[key];
count = [n unsignedIntegerValue];
});
return count;
}
The copy is important when using thread unsafe objects, with this you could avoid the possible error because of unintended release of the variable. No need for thread safe entities.
If more queue would like to use the NSMutableDictionary declare a private queue and change the setter to:
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_barrier_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = #(count);
}
});
}
IMPORTANT!
You have to set an own private queue without it the dispatch_barrier_sync is just a simple dispatch_sync
Detailed explanation is in this marvelous blog article.
In some cases you might NSCache class. The documentation claims that it's thread safe:
You can add, remove, and query items in the cache from different threads without having to lock the cache yourself.
Here is article that describes quite useful tricks related to NSCache