目标 C: 在哪里删除非国家标准化组织通知的观察员?

我有一个目标 C 类,在其中,我创建了一个 init 方法,并在其中设置了一个 NSNotification

//Set up NSNotification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(getData)
name:@"Answer Submitted"
object:nil];

在这节课中,我应该把 [[NSNotificationCenter defaultCenter] removeObserver:self]设置在哪里?我知道对于 UIViewController,我可以把它添加到 viewDidUnload方法中,那么如果我只创建一个目标 c 类,需要做什么呢?

86259 次浏览

In general I put it into the dealloc method.

The generic answer would be "as soon as you no longer need the notifications". This is obviously not a satisfying answer.

I'd recommend, that you add a call [notificationCenter removeObserver: self] in method dealloc of those classes, which you intend to use as observers, as it is the last chance to unregister an observer cleanly. This will, however, only protect you against crashes due to the notification center notifying dead objects. It cannot protect your code against receiving notifications, when your objects are not yet/no longer in a state in which they can properly handle the notification. For this... See above.

Edit (since the answer seems to draw more comments than I would have thought) All I am trying to say here is: it's really hard to give general advice as to when it's best to remove the observer from the notification center, because that depends:

  • On your use case (Which notifications are observed? When do they get send?)
  • The implementation of the observer (When is it ready to receive notifications? When is it no longer ready?)
  • The intended life-time of the observer (Is it tied to some other object, say, a view or view controller?)
  • ...

So, the best general advice I can come up with: to protect your app. against at least one possible failure, do the removeObserver: dance in dealloc, since that's the last point (in the object's life), where you can do that cleanly. What this does not mean is: "just defer the removal until dealloc is called, and everything will be fine". Instead, remove the observer as soon as the object is no longer ready (or required) to receive notifications. That is the exact right moment. Unfortunately, not knowing the answers to any of the questions mentioned above, I cannot even guess, when that moment would be.

You can always safely removeObserver: an object multiple times (and all but the very first call with a given observer will be nops). So: think about doing it (again) in dealloc just to be sure, but first and foremost: do it at the appropriate moment (which is determined by your use case).

-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}

If the observer is added to a view controller, I strongly recommend adding it in viewWillAppear and removing it in viewWillDisappear.

*edit: This advice applies to iOS <= 5 (even there you should be adding in viewWillAppear and removing in viewWillDisappear - however the advice applies if for some reason you've added the observer in viewDidLoad)

If you've added the observer in viewDidLoad you should remove it in both dealloc and viewDidUnload. Otherwise you'll end up adding it twice when viewDidLoad is called after viewDidUnload (this will happen after a memory warning). This isn't necessary in iOS 6 where viewDidUnload is deprecated and won't be called (because views are no longer automatically unloaded).

In my opinion, the following code makes no sense in ARC:

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}

In iOS 6, there's also no sense in removing observers in viewDidUnload, because it has been deprecated now.

To sum up, I always do it in viewDidDisappear. However, it depends on your requirements also, just like @Dirk said.

Note : This has been tested and working 100% percent

Swift

override func viewWillDisappear(animated: Bool){
super.viewWillDisappear(animated)
    

if self.navigationController!.viewControllers.contains(self) == false  //any other hierarchy compare if it contains self or not
{
// the view has been removed from the navigation stack or hierarchy, back is probably the cause
// this will be slow with a large stack however.
        

NSNotificationCenter.defaultCenter().removeObserver(self)
}
}

PresentedViewController:

override func viewWillDisappear(animated: Bool){
super.viewWillDisappear(animated)
    

if self.isBeingDismissed()  //presented view controller
{
// remove observer here
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}

Objective-C

In iOS 6.0 > version , its better to remove observer in viewWillDisappear as viewDidUnload method is deprecated.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

There is many times its better to remove observer when the view has been removed from the navigation stack or hierarchy.

- (void)viewWillDisappear:(BOOL)animated{
if (![[self.navigationController viewControllers] containsObject: self]) //any other hierarchy compare if it contains self or not
{
// the view has been removed from the navigation stack or hierarchy, back is probably the cause
// this will be slow with a large stack however.
        

[[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
}
}

PresentedViewController:

- (void)viewWillDisappear:(BOOL)animated{
if ([self isBeingDismissed] == YES) ///presented view controller
{
// remove observer here
[[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
}
}

The accepted answer is not safe and could cause a memory leak. Please do leave the unregister in dealloc but also deregister in viewWillDisappear (that is of course if you register in viewWillAppear)....THAT'S WHAT I DID ANYWAYS AND IT WORKS GREAT! :)

I think I found a reliable answer! I had to, as the answers above are ambiguous and seem contradicting. I looked through Cookbooks and Programming Guides.

First, the style of addObserver: in viewWillAppear: and removeObserver: in viewWillDisappear: does not work for me (I tested it) because I am posting a notification in a child view controller to execute code in the parent view controller. I would only use this style if I was posting and listening for the notification within the same view controller.

The answer I will rely on the most, I found in the iOS Programming: Big Nerd Ranch Guide 4th. I trust the BNR guys because they have iOS training centers and they are not just writing another cookbook. It is probably in their best interest to be accurate.

BNR example one: addObserver: in init:, removeObserver: in dealloc:

BNR example two: addObserver: in awakeFromNib:, removeObserver: in dealloc:

…when removing observer in dealloc: they don’t use [super dealloc];

I hope this helps the next person…

I am updating this post because Apple now has almost completely gone with Storyboards so the above mentioned may not apply to all situations. The important thing (and the reason I added this post in the first place) is to pay attention if your viewWillDisappear: is getting called. It wasn't for me when the application entered background.

In swift use deinit because dealloc is unavailable:

deinit {
...
}

Swift documentation:

A deinitializer is called immediately before a class instance is deallocated. You write deinitializers with the deinit keyword, similar to how intializers are written with the init keyword. Deinitializers are only available on class types.

Typically you don’t need to perform manual clean-up when your instances are deallocated. However, when you are working with your own resources, you might need to perform some additional clean-up yourself. For example, if you create a custom class to open a file and write some data to it, you might need to close the file before the class instance is deallocated.

Since iOS 9 it's no longer necessary to remove observers.

In OS X 10.11 and iOS 9.0 NSNotificationCenter and NSDistributedNotificationCenter will no longer send notifications to registered observers that may be deallocated.

https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11NotificationCenter

It is important to notice also that viewWillDisappear is called also when the view controller present a new UIView. This delegate simply indicate that the view controller main view is not visible on the display.

In this case, deallocating the notification in viewWillDisappear may be inconvenient if we are using the notification to allow the UIview to communicate with the parent view controller.

As a solution I usually remove the observer in one of these two methods:

- (void)viewWillDisappear:(BOOL)animated {
NSLog(@"viewController will disappear");
if ([self isBeingDismissed]) {
NSLog(@"viewController is being dismissed");
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
}
}


-(void)dealloc {
NSLog(@"viewController is being deallocated");
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
}

For similar reasons, when I issue the notification the first time, I need to account for the fact that any time a view with appear above the controller then viewWillAppear method is fired. This will in turn generate multiple copy of the same notification. Since there isn't a way to check if a notification is already active, I obviate the problem by removing the notification before adding it:

- (void)viewWillAppear:(BOOL)animated {
NSLog(@"viewController will appear");
// Add observers
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"imageGenerated" object:nil]; // This is added to avoid duplicate notifications when the view is presented again
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedImageFromCameraOrPhotolibraryMethodOnListener:) name:@"actionCompleted" object:nil];


}

This is the only correct answer (all other are mistakenly suggest to use deinit and dealloc - this is clear misunderstanding of class instance lifecycle and iOS system).

Swift 5

There are two cases of using notifications:

  • they are needed only when the view controller is on screen (viewWillAppear + viewWillDisappear)
  • they are needed always, even if user opened another screen over current (viewDidLoad + viewWillDisappear with "if").

For the first case the correct place to add and remove observer are:

    /// Add observers
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(...)
}


/// Remove observers
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)


// remove when screen dismissed
NotificationCenter.default.removeObserver(self)
}

for the second case the correct way is:

    /// Add observers
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(...)
}


/// Remove observers
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)


// remove only when view controller is removed disappear forever
if self.isBeingDismissed
|| !(self.navigationController?.viewControllers.contains(self) ?? true) {
NotificationCenter.default.removeObserver(self)
}
}

And never put removeObserver in deinit{ ... }, or dealloc - it's a MISTAKE!

override func viewDidLoad() {   //add observer
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector:#selector(Yourclassname.method), name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}


override func viewWillDisappear(_ animated: Bool) {    //remove observer
super.viewWillDisappear(true)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}