Synchronizing UI, MKMapView and CLLocationManager

2019-08-09 23:16发布

I have a ViewController that is set as delegate of MKMapView and CLLocationManager. This ViewController also receives actions from UI buttons.

I have a state machine that needs to be updated whenever some UI action happens or when a new location is available (whether it comes from MKMapView or CLLocationManager)

Obviously, the state machine update must be atomic, but I can't seem to find a good mechanism to do so.

Here's a snippet of the code:

m_locationSerialQueue = dispatch_queue_create("locationSerialQueue_ID", NULL);
m_fsmSerialQueue = dispatch_queue_create("fsmSerialQueue_ID", NULL);

...

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation
{
    dispatch_sync(m_locationSerialQueue, ^{
        ...
        [self updateFSMWithAction:LocationUpdated];
    });
}

- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
    dispatch_sync(m_locationSerialQueue, ^{
        ...
        [self updateFSMWithAction:LocationUpdated];
    });
}

- (IBAction)someButton:(id)sender
{
    ...
    [self updateFSMWithAction:buttonPressed];
}

- (void)updateFSMWithAction:(enum action_t)action
{
    dispatch_sync(m_fsmSerialQueue, ^{
        ...
    });
}    

I logged [NSThread currentThread] and all my logs have the same thread as currentThread (that is, neither MKMapView nor CLLocationManager are calling the delegate methods from different threads, it seems there is only one thread) which, unless I am missing something, will obviously prevent me from using any synchronization mechanism.

EDIT_Nov19: added more code to show the use of serial GCD queues, however I occasionally get deadlocks (most of the time when interacting with the UI), which is likely to be happening because everything is happening on a single thread, right?

According to what I read (and understood) from the doc, it should be valid to do the equivalent of:

dispatch_sync(firstSerialQueue, ^{
    dispatch_sync(secondSerialQueue, ^{
        ...
    });
});

But maybe I'm missing something regarding GCD? Actually, I'm new to it and I would prefer using standard stuff like threads, mutexes, semaphores, etc. But I'm mainly interested on a generic solution because I doubt I am the first one to encounter these kind of issues.

Are there any good practices to follow in order to synchronize delegates?

EDIT_Nov21: I've been trying to imagine how does a Cocoa app works and drawing the parallel with a basic C program on any OS, and I think I'm beginning to understand what could be happening. A C program has a main(...) function and when the app is launched, the OS creates a process (not going into details) and then calls main(...). When main returns, the OS undoes what it did to create the process.

Cocoa takes over the main and enters into a loop (which is only exited if the app tells Cocoa it will exit). Most likely this master loop handles I/O (touch, mouse, display), and when there are events (touches), it automagically calls the view controller associated with the current view. I say automatically because there must be lots happening under the hood, like any time a view is added to the view hierarchy, it's probably added to some internal queue of that master loop.

The master loop goes to its queue and checks if there's something to signal, if it is, it calls the appropriate functions. So far, this could be just one big thread.

Now, some objects (like CLLocationManager) must have their own threads, but apparently, instead of calling the delegates from their own threads, they must be somehow adding the calls to some queue on the master loop, which then goes on to call the functions. This would explain why all my functions are called from within the same thread (this might seem "duh!" for many Cocoa experienced people, but I find that quite surprising and the opposite of intuitive)

Is it a convention to call all delegates on the 'master loop'?

I would imagine that the master loop also handles the display updates, and thus can process queued operations at once (does it stops accepting requests or does it work with a copy of the queue?, it probably does something like that in order to avoid having a corrupted display update), resulting in a sort of "atomic" update.

If that's the case, then it could make sense that the calls to the delegates are scheduled to happen on the master loop, just in case an UI update is made.

However, if CLLocationManager can setup a call to its delegate to happen from within the master loop, why couldn't the UI objects do the same under the hood? Furthermore, they are probably adding operations to some queue of the master loop anyway.

EDIT_Nov21_2: Maybe scheduling the calls to the delegates to happen from within the master loop they are attached to is to avoid having people use mutexes, etc. to sync calls? Although it is a possibility, it does not seems consistent with my logs. Have to keep digging.

0条回答
登录 后发表回答