NSNotificationCenter removeObserver: in dealloc and thread-safety - objective-c

I'm using ARC and I'm calling [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; in observer's dealloc.
From NSNotificationCenter Class Reference
Be sure to invoke this method (or removeObserver:name:object:) before
notificationObserver or any object specified in
addObserver:selector:name:object: is deallocated.
NSNotificationCenter does not retain the observer.
Q1: Is NSNotificationCenter thread-safe?
In case, the observer is being deallocated(and removing observer from the notification center) and another thread post a notification at the same time.
I encounter random crash and I suspect this is the case.
Q2: Is this situation possible?
Q3: Does it lead to EXC_BAD_ACCESS?
Q4: Then, is it safe to call [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; in observer's dealloc?
Q5: If it is not safe, where should I call removeObserver:?

I just stumbled into this problem myself: I had one notification just in the process of being sent (which always happens in the main thread) while the object was in the process of being deallocated from a background thread. I fixed it by simply performing removeObserver in the main thread and waiting:
- (void)removeNotificationCenterObserver
{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
- (void)dealloc
{
[self performSelectorOnMainThread:#selector(removeNotificationCenterObserver) withObject:self waitUntilDone:YES];
}
This waits until the current run loop cycle ends and executes this message at the beginning of the next run loop cycle. This ensures that any functions that are still running will finish.

Yes, NSNotificationCenter doesn't retain observer, but it still has a pointer to it in it's dispatch table.
Q1: Quoting Apple docs
Regular notification centers deliver notifications on the thread in which the notification was posted. Distributed notification centers deliver notifications on the main thread. At times, you may require notifications to be delivered on a particular thread that is determined by you instead of the notification center. For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.
Q2,3: Yes.
Q4,5: AFAIK it's safe unless you stumble into circular reference.
I usually add/remove in -viewWillAppear:/-viewWillDisappear: for UIViewControllers and -init/dealloc for other classes.

I've wondered the same thing, and I can't find it documented. Here's what I think is going on.
removeObserver: is not thread safe in the way that you want it to be.
Think about the following situation. The last reference to the observer is released while executing code on thread A. Thread A will call the observer's dealloc method. At the same time, the observed object executes a [NSNotificcationCenter postNotificationName:object:] from thread B. This leads to an unavoidable race condition. That is, a notification will be in flight while your object is within its dealloc method.
- (void)init {
...
[[NSNotificcationCenter defaultCenter] addObserver:self
selector:#selector(callback:)
name:#"whatever"
object:nil];
...
}
- (void)dealloc {
// If the observed object posts the notification on thread B while
// thread A is here, there's a race! At best, thread B will be
// in callback: while thread A is here in dealloc. That's probably
// not what you expect. The worst case is that thread B won't make
// make it to callback: until after thread A completes the dealloc
// and the memory has been freed. Likely crash!
[[NSNotificationCenter defaultCenter] removeObserver:self];
// If the observed object does the post when thread A is here,
// you'll be fine since the observation has been removed.
}
This isn't a problem for main thread objects that are only observing other main thread objects since, by definition, you can't get into the thread A and B scenario I described.
For multi-threaded cases, the only way to guarantee you'll avoid the problem is to ensure that the observation stops before the observer's refcount hits 0. If someone else is responsible for the observer's lifetime (i.e. you have any sort of term or close method), it's easy. If not, I don't know of a solution.

Related

Remove an NSNotification observer that may not exist

I can not seem to find a definitive answer on this topic.
Is it okay to remove an observer that may not exist?
Example Code:
-(void)commonInit{
[[NSNotificationCenter defaultCenter]removeObserver:self];
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(userDidChangePrecision:)
name:kUser_Changed_Precision
object:nil];
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter]removeObserver:self];
[super dealloc];
}
This would prevent more than one observer being initialized for the object in the case where the object may be reinitialized during run time.
Snippet from the Apple docs:
- (void)removeObserver:(id)notificationObserver
Parameters
*notificationObserver*
The observer to remove. Must not be nil.
- (void)removeObserver:(id)notificationObserver name:(NSString *)notificationName object:(id)notificationSender
Parameters
*notificationObserver*
Observer to remove from the dispatch table. Specify an observer to remove only entries for this observer. Must not be nil, or message will have no effect.
In both cases, the warning that observer not be nil is overstated; the effect, in both cases, is that this message has no effect. Neither compiler nor runtime errors, no zombies, &c.
Likewise, specifying an observer that is not observing also has no effect.
Not a definitive answer, but based on observations and investigations of playing with trial-and-error code such as:
[[NSNotificationCenter defaultCenter] removeObserver:nil];
[[NSNotificationCenter defaultCenter] removeObserver:[UIView new]];
I can't find definitive documentation on if it's allowed to remove non-existent observers but I think the NSNotificationCenter documentation can be read in this way. It says that removeObserver:name:object: removes matching observers. I'm just assuming that this includes no matching observers.
But here's another reason why your approach might be harmful: When your commonInit method is being called other code (sub- or superclasses' init) might already have registered to notifications. When subclassing a UIViewController that's even likely (for memory warnings).
So I'd say you should never unconditionally unregister from notification center, except in dealloc.

how to receive NSWorkspace and accessibility notifications on another thread

I am trying to do window management, but I need the code running on a separate thread.
The first thing I need to do is subscribe to app notifications like this:
NSNotificationCenter *nc = [[NSWorkspace sharedWorkspace] notificationCenter];
NSString *not = NSWorkspaceDidLaunchApplicationNotification;
[nc addObserver:self selector:#selector(appLaunched:) name:not object:nil];
But if I simply call addObserver on another thread, will the notifications be delivered there instead?
Apple has this reference, but it seems overcomplicated:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Notifications/Articles/Threading.html
If the answer to the first question is no, then why couldn't I just forward the message like this?
NSThread *other;
- (void)appLaunched:(NSNotification*)not {
if([NSThread currentThread] != otherThread)
[self performSelector:#selector(appLaunched:) onThread:other withObject:not waitUntilDone:NO];
else
// do respond to notification
}
The second thing I need to do is add an AXObserver to a runloop on the other thread.
If I call CFRunLoopGetCurrent() from another thread, will a run loop automatically be created like calling [NSRunLoop currentRunLoop] or do a I have to create one?
Observers which are registered using -addObserver:selector:name:object: receive the notification on the thread where it's posted, not where they registered. There's also -addObserverForName:object:queue:usingBlock:, which causes the notification to be received on the specified queue, but that doesn't let you make it arrive on a specified background thread. (Only the main queue is tied to a thread.)
You can shunt a notification to another thread in the manner you suggest. However, the original receiving thread has to be idling to receive the notification in the first place. Or, rather, it has to be idling in order to allow NSWorkspace to detect the condition which causes it to post the notification.
All threads create a runloop for themselves as soon as it's requested. It's basically impossible to observe a thread not having a runloop, so you might as well just act as though the runloop is created when the thread is created.
All of that said, your original goal – "I am trying to do window management, but I need the code running on a separate thread" – is problematic. Many GUI manipulations are not legal from background threads. Also, why do you "need" to do it from a background thread? And if your main thread is not free, you're not going to receive the workspace notifications in the first place.

Is dispatch_async(dispatch_get_main_queue(), ...) necessary in this case?

I came across this piece of code, and I can't quite figure out why the author did this. Take a look at this code:
someMethodStandardMethodUsingABlock:^() {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:"notif" object:nil];
});
}];
I have a method with a completion block, and in this block a notification has to be posted. I don't quite understand why the dispatch_async on the main queue is necessary in this case. The block will already be run on the main thread, and even if it wasn't I don't think it would really matter would it? I would simply have written this:
someMethodStandardMethodUsingABlock:^() {
[[NSNotificationCenter defaultCenter] postNotificationName:"notif" object:nil];
}];
And it does work in my testing.
If you can help me shed some light on this, I'd really appreciate it!
Matt
These 2 sentences from the NSNotificationCenter Class Reference suggest a couple of possible reasons:
A notification center delivers notifications to observers
synchronously. In other words, the postNotification: methods do not
return until all observers have received and processed the
notification.
...
In a multithreaded application, notifications are always delivered in
the thread in which the notification was posted, which may not be the
same thread in which an observer registered itself.
So perhaps (a) the author doesn't want the code to block until all observers have processed the notification, and/or (b) he wants to ensure that the observer methods run on the main thread.
Sometimes you need to run methods that fire some execution asynchronously and return right away. E.g. some of the AppDelegate 'key' methods like applicationDidBecomeActive, or applicationDidEnterBackground, need to be executed and return quickly so the OS doesn't kill your app.
I don't know if that is the case of your question, but it is a possible explanation of the usage of dispatch_async.

NSManagedObject: create on separate thread

I understand that Core Data is not thread safe and that NSManagedObjectContext and NSManagedObjects associated with a context cannot be passed from thread to thread.
Question:
However, if I have a NSManagedObjectContext on my main thread, can I create a NSManagedObject object on a background thread (WITHOUT attaching it to any context -- that is, simply call alloc/init on NSManagedObject), then pass that NSManagedObject back to the main thread and add it to the context from there? I've reviewed the docs on Core Data concurrency, but can't find anything that says this usage pattern is okay.
Context:
I have a background thread that performs a complex task and then publishes a result. The result is an NSManagedObject subclass that contains a few attributes: time, a file path, and a success or error message (as a string). I want to create the result object on the background thread, then toss it back to the main thread and add it to the Core Data context where it will be displayed in a tableView.
If I can't create the managedObject on the background thread, then I'll need to create a dictionary, pass the dictionary to the main thread, read the keys, create the managedObject from those values, etc. Just seems cleaner to create the managedObject on the background thread if possible.
The better approach is to have a context per thread. This way each thread will have its own scratch pad to play around with. Then when you background thread finishes, tell your main thread to update its view or ui table view or how every you are presenting your data.
You'll need to notify your main thread when changes occur. The big problem is, the contexts between different threads and main thread are not aware of each other. There is a way in core data to keep the contexts in sync. When you want to save, the context on the background thread should broadcast an NSManagedObjectContextDidSaveNotification notification.
So for example, in your main method in your NSOperation, you could do this:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:context];
the mergeChanges would be a private method in your NSOperation instance.
example of merge changes
- (void)mergeChanges:(NSNotification *)notification
{
ApplicationController *appController = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appController managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}

Removing an Observer

In a NSManagedObject Sub Class I have the code …
- (void) awakeFromInsert {
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:NSKeyValueObservingOptionNew context:nil];
}
Which adds my App Delegate as an Observer, what I want to do now is from inside my App Delegate, I want to remove itself as an Observer for my NSManagedObject Sub Class.
How would I do this?
Thanks.
I was thinking of adding this to my App Delegate
[JGManagedObject removeObserver:self forKeyPath:#"name"];
but unfortunately removeObserver:forKeyPath: is not a Class Method.
For something like this, it's probably best to rethink the design. The delegate, in this case, would have to have some specific knowledge of the managed object itself in order to do this -- and the delegate would have to have some idea about when in the lifecycle it should (or would want to) stop observing the object.
You have a few choices. Instead of doing this in awake from insert, you could have the delegate start observing it when it creates it and then stop observing it when it gives up ownership. If that is not feasible in your design, you could have the object remove its observer when it is deallocated. If this is a fire-and-forget (basically the delegate only cares once), you could remove the observer after the first change notification. Since, however, you created the observation within the creation lifecycle of this object, it is probably best to remove that observation at the destruction of the object:
- (void)dealloc
{
[self removeObserver:[NSApp delegate] forKeyPath:#"name"];
// other clean-up
[super dealloc];
}
You might also want to do this when the object awakes from fetch and from fault and release the observer when the object will become a fault.
Much the same way you added the observer in the first place, only with fewer options:
// Given some managed object "object"...
[object removeObserver:self forKeyPath:#"name"];
Note that we remove self as the observer, rather than the application delegate as given by [NSApp delegate], since the code will be running within the delegate itself.
How about sending your object the removeObserver:forKeyPath message just before you delete it from the ManagedObjectContext?
From iOS 11 and above, we have automatic registration of KVO. From the Foundation Release Notes for macOS 10.13 and iOS 11
Relaxed Key-Value Observing Unregistration Requirements
Prior to 10.13, KVO would throw an exception if any observers were still registered after an autonotifying object's -dealloc finished running.
Additionally, if all observers were removed, but some were removed
from another thread during dealloc, the exception would incorrectly
still be thrown. This requirement has been relaxed in 10.13, subject
to two conditions:
• The object must be using KVO autonotifying, rather than manually
calling -will and -didChangeValueForKey: (i.e. it should not return NO
from +automaticallyNotifiesObserversForKey:) • The object must not
override the (private) accessors for internal KVO state
If all of these are true, any remaining observers after -dealloc
returns will be cleaned up by KVO; this is also somewhat more
efficient than repeatedly calling -removeObserver methods.