I'm observing iTunes using the NSDistributedNotificationCenter class.
This method is called when iTunes plays a new track. The implementation is empty.
Strangely, when I have ARC enabled, there is some memory allocated every time this method observeITunes: method gets called. Apparently, this memory is never released.
No matter how long I wait, this memory is never released.
So I found this strange and made an empty test-project, copying the relevant code there.
In the test-project there is no memory allocation visible at all (Which is correct).
Code
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:#selector(observeITunes:)
name:#"com.apple.iTunes.playerInfo"
object:nil];
- (void)observeITunes:(NSNotification *)notification {
// Empty method
}
Can anyone explain this phenomenon to me?
I couldn't find an answer.
Related
I'm trying to make an Objective-C iOS library work for macOS applications. Removing UIKit-based notifications is the only real task here. When trying to replace—for example—UIApplicationDidEnterBackgroundNotification with NSApplicationDidResignActiveNotification, I run into the odd error of the latter variable being called an undeclared identifier.
[self.observers addObject: [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
// stop operations
}]];
The same error does not arise when I use the alternate method:
[[NSNotificationCenter defaultCenter] addObserver:[???] selector:[some selector] name:NSApplicationDidResignActiveNotification object:nil];
The problem with this method though is that I have to have an observer beforehand—like self—as opposed to receiving one that I can add to the self.observers array.
I've read a smattering of docs, questions, and guides—including the NSApplication doc and the NSHipster guide—but I can't seem to figure out my misunderstanding, though I believe it is something fundamental about NSNotificationCenter and how it works.
Silly solution, though aided by #Willeke's comment that they weren't seeing an error with the same code snippet—the library needed Cocoa.h in place of the iOS imports.
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.
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.
-(void)viewDidUnload
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:LASTUPDATEDLOCATION object:nil];
[self setHeaderViewofWholeTable:nil];
[self setFooterViewofWholeTable:nil];
[self setHeaderActivityIndicator:nil];
[self setFooterActivityIndicator:nil];
[self setLastUpdated:nil];
[self setLblPullDowntoRefresh:nil];
[self setRefreshArrow:nil];
[self setContainerForFormerHeader:nil];
[self setFooterContainer:nil];
[super viewDidUnload];
}
I thought viewDidLoad is called the view itself goes nil. When we set the view to nil, wouldn't all those things automatically become nil?
What am I misunderstanding?
Before ARC you needed to manually release objects that you allocated. Setting a property that is marked retain to nil does the releasing. This is no longer necessary when you use the Automatic Reference Counting (ARC) feature, which is on by default in the compiler that comes with recent versions of Xcode.
Good news. As of iOS 6, viewDidUnload has been deprecated. In iOS 5 and earlier, when memory was low there was a chance that your view might have been unloaded (and to make sure there were no memory leaks, you released IBOutlets in this method). But this is no longer called in iOS 6, and thus, no longer a requirement.
Now if there is a issue with memory, your view controller can override:
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
Some of the other answers cover some of this but there is more to this. A view controller will have its viewDidLoad method called. Typically this results in IBOutlets being retained and possibly lots of other views and objects being allocated and retained. If all goes well, eventually the view controller is deallocated and all of those retained objects need to be released.
That's the simple, happy path. Under low memory conditions, in iOS 5 and earlier, it is possible that a view controller's view will be unloaded. The viewDidUnload method was a chance for you to clean up all of the other objects that were retained as part of the viewDidLoad process. And here's the main reason - at some point, viewDidLoad may be called again to redisplay the view controller's view.
Most people write their viewDidLoad method like it will only ever be called once. And this is OK if the viewDidUnload method properly clears up objects. If it doesn't, the next call to viewDidLoad will result in a bunch of memory leaks.
ARC pretty much eliminated the issue with the memory leaks if you didn't clean things up properly in viewDidUnload. But viewDidUnload was still helpful for cleaning up memory when needed.
As was mentioned, as of iOS 6, a view controller's view in never unloaded in low memory conditions and the viewDidUnload (and viewWillUnload) methods have been deprecated.
If your app still supports iOS 5 along with iOS 6, you still need to make proper use of viewDidUnload. But if you want to free up memory when needed, use didReceiveMemoryWarning.
We set so many things to nil to free up as much memory we can and reduce processor strain and increase battery life, not all objects automatically remove themselves from the queue.
I'm not sure if I'm doing something wrong here:
I'm registering for Workspace notifications using this snippet in awakeFromNib
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector:#selector(noteReceived:)
name:nil
object:nil];
the selector noteReceived: takes a single NSNotification * as a parameter. And I've got a breakpoint on it.
When compiled with GC turned off it works fine, and I receive all notifications.
When complied with GC turned on, I only get one notification when my app launches, and that's it.
Am I missing something?
Solution:
I was missing something. This was just a quick test project so there wasn't the usual connection between controllers that there would be in a real app. It isn't enough to instantiate an object in a nib/xib file and expect it not to be collected.
Once I made my controller a delegate of File's owner (even though it doesn't implement any delegate methods) that was enough to keep the object alive.
Under GC, NSNotificationCenter only maintains a weak reference to your observing object. Because of that, make sure that your observing object is rooted somewhere in your object hierarchy.