Question

I have a simple app to work with i/o to plist file in several threads from 2 views. So now I have a field with UUID in plist and when I delete the item - DataManager generates the message via NSNotificationCenter with the UUID of the deleted object,

[[NSNotificationCenter defaultCenter] postNotificationName:EADataManagerItemDeleted
                                                            object:nil
                                                          userInfo:userInfo];

so the view is listening the message and if the received UUID is the same as the view displaying - the system can react to this.

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(receiveNotification:)
                                             name:EADataManagerItemDeleted
                                           object:nil];

But I have the information that we use NSNotificationCenter in very difficult cases when we have no idea how to get the object in another way. So please, how can I understand that the item on screen was changed in another thread? Thanks. My TechLead told me to use NSException class for this purposes, but can't see the solution anyway.

Was it helpful?

Solution 2

Please, please, please do not try to use NSExceptions for view invalidation. I'm not even sure how that would work, but please don't try. Unlike in other frameworks/languages (like Java or .NET), in Cocoa, exceptions usually mean "something has gone horribly wrong and the app should terminate." (Yes, there are a handful of exceptions to that rule, but "view invalidation" isn't one of them.)

The first thing to know is that UI (i.e. views) objects are typically regarded as main-thread only. So if you're mutating your model on a background thread, the invalidation of the view should be marshaled to the main thread. GCD has facilities to handle this (dispatch_get_main_queue) but there are others as well, such as -[NSObject performSelectorOnMainThread:...] and CFRunLoopPerformBlock.

The next thing to know is that if you're going to do this (i.e. read your model on the main thread to populate the UI, and update your model from background threads) you will need to have some sort of locking system to be sure that no background threads are mutating the model while the main thread is trying to read from it. You can marshal invalidation to the main thread without a lock, but when the views go to re-validate (i.e. layout and draw) you will want to take an appropriate lock for the model objects being represented to prevent data corruption. Managing these locks can rapidly get non-trivial. (Although private concurrent GCD queues accessed with dispatch_(a)sync & dispatch_barrier_(a)sync are a pretty good place to start.)

One common pattern that avoids most of the locking complexity is to have background threads do their work on a copy of the model and then send not only the view invalidation, but also the model mutation operation back to the main thread, so in terms of the "one, true model", it is always main thread only, in which case you don't need to worry about locking. If you can do things this way, it will save you a LOT of grief.

On OSX (but not iOS) there are Cocoa bindings, which use Key-Value Observing (with a bunch of other opaque "magic" in between) to automatically invalidate and update bound UI when the model changes. Cocoa bindings don't exist on iOS, but it's worth mentioning in that KVO does and the notifications it sends could be used for view invalidation. The important thing to know with that is that KVO notifications are sent synchronously on the thread that the mutation takes place on, so again, you would want to marshal your mutations back to the main thread.

Lastly, using NSNotificationCenter doesn't free you from worrying about locking and cross-thread notifications either because, like KVO, NSNotifications are delivered synchronously on the thread on which they're posted. Sending an NSNotification on a background thread for which there are UIViews observers is going to cause problems too.

OTHER TIPS

Use KVO (Key-Value-Observing).

There's a nice and in-depth article at NSHipster: http://nshipster.com/key-value-observing/

With Cocoa (on the desktop), you can use bindings with Interface Builder, on Cocoa Touch (iOS), you need to use it programmatically, but it's relatively easy once you know how:

In short you can opt in to track any property of known objects. In your case, all this code goes into your ViewController. I assume (and recommend) that your viewController has a reference to your model object (dataManager). Though your model mustn't have any reference back to your viewController; that's where you'll use Key-Value-Observing. In other words: your view controller will observe value changes in your model's properties (-> a.k.a "keys").

First, register for KVO:

[dataManager addObserver:self
              forKeyPath:@"nameOfThingIAmInterestedIn"
                 options:NSKeyValueObservingOptionNew
                 context:NULL];

don't forget to remove the observation (e.g. in dealloc)

[dataManager removeObserver:self forKeyPath:@"nameOfThingIAmInterestedIn"];

and finally implement this method to receive KVO notifications. In your case you should dispatch_async on the main queue as you want to update your view (on the main thread):

-(void)observeValueForKeyPath:(NSString *)keyPath 
                     ofObject:(id)object
                       change:(NSDictionary *)change
                      context:(void *)context 
{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"KVO: %@ changed property %@ to value %@", object, keyPath, change);
    }
}

Notifications are quite similar, but in a document based environment, you have notifications app-wide, so they cross document boundaries. KVO is a much smarter and safer way to get notifications about changed properties.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top