Search code examples
iosobjective-casynchronousobjective-c-blocks

Failing to see how @synchronized works with asynchronous blocks


Suppose I have a function like the following one, that is called 30 times per second (this code is a simplification of a more complex code created by Apple that I am trying to understand. The idea of Apple's original code is this: like I said doSomethingWithImage is called 30 per second. So, the whole method has 1/30 of a second to do everything but imagine that doSomethingThatWillTakeTime takes more than expected and doSomethingWithImage is called again, while doSomethingThatWillTakeTime is already being executed. If this is the case, the code has to drop that image and do nothing.

// code...
@synchronized (self);
  [self doSomethingWithImage:image];
}

// ...

// call under @synchronized( self )
- (void)doSomethingWithImage:(UIImage *)image
{
    self.myImage = image;

    [self executeBlockAsync:^{ 

        UIImage *myImage = nil;

        @synchronized( self ) 
        {
            myImage = self.myImage; 
            if (myImage) { 
                self.myImage = nil; // drop the image
            }
        }

        if ( myImage ) { 
            [self doSomethingThatWillTakeTime:myImage];  
        }
    }];
}


 (void)executeBlockAsync:(dispatch_block_t)block
{
    dispatch_async( _myQueue, ^{
            callbackBlock();
    } );
}

My problem is that looking at this code I cannot see how this is doing that. I do not have a mental image of what lines are being execute in which order for that to occur. Probably I do not understand the role of @synchronized in doing that, on what exactly is being locked or not.


Solution

  • The only thing that @synchronized does is to ensure "thread safe", synchronized interaction with some objects or variables. Specifically, it ensures that you don't access these resources from one thread while they're possibly being mutated elsewhere by another thread. If one thread is inside the @synchronized(self) {...} block and another thread encounters its own @synchronized(self) {...} block, this latter code won't start running that until the other thread finishes its @synchronized block.

    This is just one of the many synchronization techniques outlined in the Threading Programming Guide: Synchronization. (Or you can also use dedicated GCD queues, either serial queues or concurrent queues that use the reader-writer pattern, to manage this synchronization as outlined in Concurrency Programming Guide: Migrating Away from Threads: Eliminating Lock-based Code. But that's a separate topic altogether.)

    In the captureOutput:didOutputSampleBuffer:fromConnection: method that you shared with us, they're just using @synchronized to ensure that the _recordingStatus and the _recorder don't change in one thread while you're using them from another thread. They accomplish this by making sure that all interactions with these variables (whether reading in one thread or updating in another) happens within their own @synchronized { ... } blocks.

    But note that @synchronized does not serve any functional purpose beyond that thread safe interaction. All of the logic regarding "should I add a frame or not" is dictated by the simple if statements in that code. The @synchronized directives have nothing to do with that. They're just there to ensure thread safety.