If you’re just getting started with threading in Objective-C, it won’t be long before you’ll need to make some thread-safe modifications to objects. One of the many useful tools that Objective-C gives us is the @synchronized
directive. From the documentation:
The
@synchronized
directive is a convenient way to create mutex locks on the fly in Objective-C code. The@synchronized
directive does what any other mutex lock would do—it prevents different threads from acquiring the same lock at the same time.
Using @synchronized
is super easy
@synchronized(key) {
// thread-safe code goes here
}
Examples!
First we’re going to create an NSMutableArray
and fill it with garbage.
bigArray = [NSMutableArray arrayWithCapacity:5];
for (int i = 0; i < 5; i++) {
[bigArray addObject:[NSString stringWithFormat:@"object-%i", i]];
}
And a method to display/modify the array.
- (void)updateBigArray:(NSString *)value {
for (int j = 0; j < bigArray.count; j++) {
NSString *currentObject = [bigArray objectAtIndex:j];
[bigArray replaceObjectAtIndex:j withObject:[currentObject stringByAppendingFormat:@"-%@", value]];
NSLog(@"%@", [bigArray objectAtIndex:j]);
}
}
Now let’s call our method on two separate threads. Notice I’m sending foo
in for both of them. I’ll explain why in a bit.
NSString *foo = @"foo";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self updateBigArray:foo];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self updateBigArray:foo];
});
This gives us the output:
object-0-foo object-0-foo object-1-foo-foo object-1-foo object-2-foo object-2-foo object-3-foo object-3-foo object-4-foo-foo object-4-foo
Both threads are working over the top of each other which could lead to app crashes depending on what you’re doing. So let’s update our method to use @synchronized
with value
as our lock key.
- (void)updateBigArray:(NSString *)value {
@synchronized (value) {
for (int j = 0; j < bigArray.count; j++) {
NSString *currentObject = [bigArray objectAtIndex:j];
[bigArray replaceObjectAtIndex:j withObject:[currentObject stringByAppendingFormat:@"-%@", value]];
NSLog(@"%@", [bigArray objectAtIndex:j]);
}
}
}
object-0-foo object-1-foo object-2-foo object-3-foo object-4-foo object-0-foo-foo object-1-foo-foo object-2-foo-foo object-3-foo-foo object-4-foo-foo
That looks a lot better. What’s happening is that our @synchronized
block effectively pauses one thread, allowing the other thread access to the block, then un-pauses it once the first has finished.
So what about the key?
This is where it gets interesting. If you pass the same object to @synchronized
as the key, it will get paused (which we just saw). However, if you send a different object than the key it will get through. Remember when we passed foo
through twice? Let’s pass something else the second time instead.
NSString *foo = @"foo";
NSString *bar = @"bar";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self updateBigArray:foo];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self updateBigArray:bar];
});
object-0-foo object-0-bar object-1-foo object-1-bar object-2-foo object-2-foo-bar object-3-foo object-3-foo-bar object-4-foo object-4-foo-bar
There could be reasons why you would want to lock on value
, but in this case it would be smarter to use a different key.
Let’s use
@synchronized (bigArray) {
...
instead of
@synchronized (value) {
...
object-0-foo
object-1-foo
object-2-foo
object-3-foo
object-4-foo
object-0-foo-bar
object-1-foo-bar
object-2-foo-bar
object-3-foo-bar
object-4-foo-bar
In the end
@synchronized
is a great shorthand mutex lock. It may not be the most efficient lock, but chances are you won’t notice. If you’re just getting started with threading, I highly recommend getting more familiar with it.