Let's say I have an NSMutableArray of objects (NSMutableArray is not thread-safe), and I have these methods on an object that contains this array (this is a simplified example for the sake of clarity):
- (void)addObject:(id)object {
if (_objectsArray == nil) {
_objectsArray = [NSMutableArray array];
}
[_objectsArray addObject:object];
if (_thread == nil) {
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(__threadEntry:) object:nil];
_thread.name = @"com.company.ThreadName";
[_thread start];
}
}
- (void)removeObject:(id)object {
[_objectsArray removeObject:object];
if (_objectsArray.count == 0) {
_isRunning = NO;
}
}
- (void)stopRendering {
_isRunning = NO;
}
- (void)__threadEntry:(id)sender {
// Set up CADisplayLink on current run loop.
// "_isRunning" is declared as a "volatile BOOL"
_isRunning = YES;
while (_isRendering) {
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// Thread cleanup.
}
- (void)__threadProc {
@autoreleasepool {
[_objectsArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// Do work
}];
}
}
So basically, I have methods that add/remove objects from the mutable array, but work on the objects in the array is performed on a different thread. i.e. addObject and removeObject are both only called from the main thread, whereas the work (in __threadProc) is done on a different thread.
As it is, this code is not thread-safe, as an object can be added/removed while enumeration is under progress in __threadProc. So what is the correct way to synchronize this?
I'm not sure if locks is the right answer here, because do locks work across different methods? For example, if I put a lock/unlock around [_objectsArray addObject:object] in the addObject method and a lock/unlock around the work in __threadProc, would that work (assuming of course that both are the same lock object (e.g. NSLock)?
Also, adding/removing objects happens very infrequently compared to how often work is done in __threadProc.
Suppose that we’re implementing a thread-safe queue in Objective-C. We might start it like this:
he ThreadSafeQueue class above has an init method which initializes two ivars: an _objectsArray array and an NSLock. It has a push: method which acquires the lock, inserts an _object into the array, and then releases the lock. Many threads can call push: at the same time, but the line [_objectsArray addObject:object] will only ever be run on one thread at a time. The steps might go something like this:
We can implement this more succinctly using the @synchronized construct:
The synchronized block has the same effect as the [_lock lock] and [_lock unlock] in the above example. You can think of it as locking on self as if self is an NSLock. A lock is aqcuired before any code after the opening { is run, and the lock is released before any code after the closing } is run. This is really handy because it means that you can never forget to call unlock!
// Or using the @synchronized construct:
You can @synchronize on any Objective-C object. So we could just as well have used @synchronized(_elements) instead of @synchronized(self) in the example above and the effect would be the same.