143

If you tell an Objective-C object to removeObservers: for a key path and that key path has not been registered, it cracks a sad, like:

Cannot remove an observer <observerObject> for the key path "theKeyPath" from <objectbeingObserved> because it is not registered as an observer.

Is there a way to determine if an object has a registered observer, so i can do this

if (object has observer){
  remove observer
}
else{
  go on my merry way
}
The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
Aran Mulholland
  • 23,555
  • 29
  • 141
  • 228
  • I got into this scenario updating an old app on iOS 8 where a view controller was being deallocated and throwing the "Cannot remove" exception. I thought that by calling `addObserver:` in `viewWillAppear:` and correspondingly `removeObserver:` in `viewWillDisappear:`, the calls were correctly paired. I have to make a quick fix so I'm going to implement the try-catch solution and leave a comment to investigate the cause further. – bneely Jun 21 '15 at 18:40
  • I'm just dealing with something similar and I see I need to look into my design more deeply and to adjust it so that I won't need to remove the observer again. – Bogdan Jun 21 '16 at 07:06
  • using a bool value like suggested in this answer worked the best for me: https://stackoverflow.com/a/37641685/4833705 – Lance Samaria Jun 11 '20 at 22:01

11 Answers11

317

Put a try catch around your removeObserver call

@try{
   [someObject removeObserver:someObserver forKeyPath:somePath];
}@catch(id anException){
   //do nothing, obviously it wasn't attached because an exception was thrown
}
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
Adam
  • 3,282
  • 1
  • 15
  • 2
  • 12
    1+ Good answer, worked for me and I agree with your rant before it was edited. – Robert Nov 17 '11 at 10:32
  • 25
    upvoted for deleted rant that I would most likely agree with. – Ben Gotow Dec 31 '11 at 02:03
  • 12
    Isn't here any other elegant solution? this one takes at least 2ms per usage... imagine it in a tableviewcell – João Nunes Oct 17 '12 at 14:01
  • 19
    Downvoted because you're omitting to say that this is unsafe for production code and likely to fail at any time. Raising exceptions through framework code is not an option in Cocoa. – Nikolai Ruhe Oct 29 '13 at 17:05
  • @MattDiPasquale's answer is significantly better.. Not only does this answer destroy the observer, but it's very slow. – brandonscript Jul 13 '14 at 20:09
  • 5
    This solution is safe for production and is very well documented. In fact Apple basically suggests using this specific pattern for exception handling. This is even faster and more robust on 64 bit systems thanks to Apple's zero cost exception handling. (personally however i'd change 'id anException' to 'NSException *exception') – Yup. Oct 22 '14 at 14:08
  • 6
    How to use this code in swift 2.1. do { try self.playerItem?.removeObserver(self, forKeyPath: "status") } catch let error as NSError { print(error.localizedDescription) } getting warning. – Vipulk617 Oct 28 '15 at 07:45
  • 2
    We can use try and catch for this special case. This what I understood from http://nshipster.com/key-value-observing/ – jailani Nov 06 '15 at 06:25
  • 2
    I am facing same issue to remove observer in swift. How to resolve this issue ? – Vipulk617 Dec 08 '15 at 12:27
  • I totally agree. I hate the smart-alecks. Tunnel vision. Seriously – funct7 Apr 25 '16 at 06:04
  • I'm with @vipulk617; I can't find a safe way to do this in Swift. – Ky - Aug 10 '16 at 18:47
  • 1
    Keep in mind that this has a big issue, if the observer is not set and this throws and exception then ARC won't release the objects in the `try` scope. http://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions If you don't consider this you could end up with memory leaks and memory increases. – pablobart Apr 12 '17 at 15:48
  • Any solution for Swift? – Mehul Thakkar Oct 03 '17 at 09:25
37

The real question is why you don't know whether you're observing it or not.

If you're doing this in the class of the object being observed, stop. Whatever's observing it expects to keep observing it. If you cut off the observer's notifications without its knowledge, expect things to break; more specifically, expect the observer's state to go stale as it doesn't receive updates from the formerly-observed object.

If you're doing this in the observing object's class, simply remember which objects you're observing (or, if you only ever observe one object, whether you're observing it). This is assuming that the observation is dynamic and between two otherwise-unrelated objects; if the observer owns the observed, just add the observer after you create or retain the observed, and remove the observer before you release the observed.

Adding and removing an object as an observer should usually happen in the observer's class, and never in the observed object's.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • 14
    Use Case: You want to remove observers in viewDidUnload, and also in dealloc. This is removing them twice and will throw the exception if your viewController is unloaded from a memory warning, and then also released. How do you suggest handling this scenario? – bandejapaisa Jan 18 '12 at 12:34
  • 2
    @bandejapaisa: Pretty much what I said in my answer: Keep track of whether I'm observing and only try to stop observing if I am. – Peter Hosey Jan 18 '12 at 23:07
  • 42
    No, that's not an interesting question. You shouldn't have to keep track of this; you should be able to simply unregister all listeners in dealloc, without caring about whether you happened to hit the code path where it was added or not. It should work like NSNotificationCenter's removeObserver, which doesn't care if you actually have one or not. This exception is simply creating bugs where none would otherwise exist, which is bad API design. – Glenn Maynard Dec 11 '12 at 00:27
  • 1
    @GlennMaynard: Like I said in the answer, “If you cut off the observer's notifications without its knowledge, expect things to break; more specifically, expect the observer's state to go stale as it doesn't receive updates from the formerly-observed object.” Every observer should end its own observation; failure to do this should ideally be highly visible. – Peter Hosey Dec 11 '12 at 06:44
  • 3
    Nothing in the question talks about removing *other* code's observers. – Glenn Maynard Dec 11 '12 at 18:21
  • @GlennMaynard: The question doesn't specify. Removing other observers is an inherent peril of removing all observers blindly. If you're your only observer(s), then it won't cause a problem. If you aren't, then you do run the risk of breaking whatever else is observing you. – Peter Hosey Dec 11 '12 at 18:58
  • @PeterHosey what if I want a view controller to observe a model's property in `viewDidLoad` and remove itself in `dealloc`. Won't that crash if `dealloc` gets called before `viewDidLoad`? I'm pretty sure that won't happen in my case since I'm simply pushing the view controller, but I guess we'll find out! :) A safer way might be to observe in `init` and guard `observeValueForKeyPath:ofObject:change:context:` with `[self viewIsLoaded]`, but that's probably superfluous in this case. Thoughts? – ma11hew28 Apr 11 '14 at 15:22
  • @PeterHosey what about automatic reference counting (ARC)? Don't you use it? What if we had ARC for KVO? Less code. More profit. – ma11hew28 Apr 11 '14 at 15:41
  • @MattDiPasquale: ARC didn't exist yet when I wrote the answer. Besides which, it isn't relevant; ARC doesn't have anything to do with KVO. If we did have “ARC for KVO” (by which I assume you mean observers automatically stopping those observations upon death), that would be convenient, but we don't. In KVO as we have it today, you must remove observations explicitly—and you should not remove observations that do not belong to you. – Peter Hosey Apr 11 '14 at 19:28
  • It shouldn't crash anyway. It just leads to additional conditional checks. – Vassilis Apr 07 '15 at 20:29
  • The fact that it crashes is bad API design in my opinion. Because of the nature of some objects (AVPlayer has no delegate, only supports KVO and it's very asynchronous by nature), it's very easy to have a situation in which AVplayer is doing something slow (loading, buffering, etc.) and you get a `deinit` _before_ your AVplayer had a chance to be stopped. It happens and it's hard to fix and reproduce. It could have been avoided by not making a huge deal and/or providing a safe way to handle KVO. – Martin Marconcini Mar 30 '16 at 00:34
  • My goodness, another answer like this. Please tell me why having another boolean property to check if you're observing and removing it only when true is a better design. Don't you have any code that checks membership of an object in an array and removes it if it is? You're basically saying that's bad design. According to your logic, you should know if it's there already instead of iterating through. – funct7 Apr 25 '16 at 06:01
  • Different situation. You'd do that if the array contents were determined at run time, such as by user input. You wouldn't need to if the array's contents are hard-coded, which is the analogous situation to an object having KVO observers that you created. You created those objects; you added the observer; you know that you added it, so you can simply remove it. And if you dynamically may or may not have added that observer, then you record that in a Boolean property upon adding and check it before removing. – Peter Hosey Jun 10 '16 at 06:03
  • again. assumptions. you seem to assume everyone is an idiot. yes we have situations where observation is determined at run time so same situation. so for arrays, if the members of an array are determined at run time it's customary to check for membership and swift provides 'contains', whereas for an observer you can't check for membership so you need a variable to tell you if you're observing? and that's not bad design? say the first sentence to everyone who checks for membership in arrays because the items are determined at run time. i really hope you realize why you're doing wrong. – funct7 Jun 10 '16 at 16:19
  • @PeterHosey what about the use case outlined in http://stackoverflow.com/questions/38526237/safe-key-value-observing-of-keypaths ? – MrTJ Jul 28 '16 at 07:42
  • Use case: a stateless program. – Ky - Aug 10 '16 at 18:41
25

FWIW, [someObject observationInfo] seems to be nil if someObject doesn't have any observers. I wouldn't trust this behavior, however, as I haven't seen it documented. Also, I don't know how to read observationInfo to get specific observers.

Ky -
  • 30,724
  • 51
  • 192
  • 308
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • Do you happen to know how I can retrieve a specific observer? `objectAtIndex:` doesn't yield desired result .) – Eimantas May 25 '12 at 12:32
  • 1
    @MattDiPasquale Do you know how can I read observationInfo in code? In prints it is coming out fine, but it is a pointer to void. How should I read it? – neeraj May 22 '13 at 14:21
  • observationInfo is debugging method documented in Xcode's debugging paper (something with "magic" in the title). You can try looking it up. I can tell that if you need to know if someone is observing your object - you are doing something wrong. Rethink your architecture and logic. Learned it the hard way .) – Eimantas Apr 13 '14 at 05:05
  • Source: `NSKeyValueObserving.h` – nefarianblack Aug 05 '15 at 16:01
  • plus 1 for a comically dead end but still somewhat helpful answer – Will Von Ullrich Jan 09 '19 at 14:57
  • after 10 years... – neeraj Nov 11 '22 at 17:28
4

The only way to do this is to set a flag when you add an observer.

Leibowitzn
  • 830
  • 4
  • 6
  • 3
    The you end up with BOOLs everywhere, better still create a KVO wrapper object that handles adding the observer and removing it. It can ensure your observer is only removed once. We have used an object just like this, and it works. – bandejapaisa Jan 18 '12 at 12:36
  • great idea if you don't always are observing. – Andre Simon Jul 28 '16 at 20:10
4

When you add an observer to an object you could add it to a NSMutableArray like this:

- (void)addObservedObject:(id)object {
    if (![_observedObjects containsObject:object]) {
        [_observedObjects addObject:object];
    }
}

If you want to unobserve the objects you can do something like:

for (id object in _observedObjects) {
    if ([object isKindOfClass:[MyClass class]]) {
        MyClass *myObject = (MyClass *)object;
        [self unobserveMethod:myObject];
    }
}
[_observedObjects removeAllObjects];

Remember, if you unobserve a single object remove it from the _observedObjects array:

- (void)removeObservedObject:(id)object {
    if ([_observedObjects containsObject:object]) {
        [_observedObjects removeObject:object];
    }
}
shim
  • 9,289
  • 12
  • 69
  • 108
Oritm
  • 2,113
  • 2
  • 25
  • 40
  • 1
    If this happens in a multi threaded world, you need to make sure your array is ThreadSafe – shrutim Nov 10 '16 at 18:52
  • You're keeping a strong reference of an object, which would increase the retain count every time an object is added in the list and won't be deallocated unless its reference is removed from the array. I would prefer using `NSHashTable`/`NSMapTable` to keep the weak references. – atulkhatri Jul 10 '19 at 09:46
3

In my opinion - this works similar to retainCount mechanism. You can't be sure that at the current moment you have your observer. Even if you check: self.observationInfo - you can't know for sure that you will have/won't have observers in future.

Like retainCount. Maybe the observationInfo method is not exactly that kind of useless, but I only use it in debug purposes.

So as a result - you just have to do it like in memory management. If you added an observer - just remove it when you don't need it. Like using viewWillAppear/viewWillDisappear etc. methods. E.g:

-(void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self addObserver:nil forKeyPath:@"" options:NSKeyValueObservingOptionNew context:nil];
}

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self removeObserver:nil forKeyPath:@""];
}

And it you need some specific checks - implement your own class that handles an array of observers and use it for your checks.

quarezz
  • 454
  • 1
  • 6
  • 11
  • `[self removeObserver:nil forKeyPath:@""];` needs to go before: `[super viewWillDisappear:animated];` – Joshua Hart Aug 31 '17 at 17:22
  • @JoshuaHart why? – quarezz Sep 01 '17 at 18:11
  • Because it's a tear down method (dealloc). When you override some kind of teardown method, you call super last. Like: `- (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }` – Joshua Hart Sep 01 '17 at 18:18
  • **viewWillDisapear** isn't a tear down method and it has no connection with dealloc. If you push forward to navigation stack, **viewWillDisapear** will be called, but your view will stays in memory. I see where you're going with the logic of setup/teardown, but doing it here will give no actual benefit. You would want to place removing before super only if you have some logic in base class, that could conflict with current observer. – quarezz Sep 04 '17 at 08:10
3

[someObject observationInfo] return nil if there is no observer.

if ([tableMessage observationInfo] == nil)
{
   NSLog(@"add your observer");
}
else
{
  NSLog(@"remove your observer");

}
Ky -
  • 30,724
  • 51
  • 192
  • 308
Anupama
  • 41
  • 4
  • Accordingly to Apple docs : observationInfo returns a pointer that identifies information about all of the observers that are registered with the receiver. – FredericK Mar 18 '16 at 09:00
  • This was better-said in [@mattdipasquale's answer](http://stackoverflow.com/a/9322342/3939277) – Ky - Aug 10 '16 at 21:04
2

The whole point of the observer pattern is to allow an observed class to be "sealed" -- to not know or care whether it is being observed. You are explicitly trying to break this pattern.

Why?

The problem you are having is that you are assuming you are being observed when you aren't. This object did not start the observation. If you want your class to have control of this process, then you should consider using the notification center. That way your class has full control on when data can be observed. Hence, it doesn't care who is watching.

Ky -
  • 30,724
  • 51
  • 192
  • 308
adonoho
  • 4,339
  • 1
  • 18
  • 22
  • 11
    He's asking how the *listener* can find out if it's listening to something, not how the object being observed can find out if it's being observed. – Glenn Maynard Dec 11 '12 at 00:27
1

I am not a fan of that try catch solution so what i do most of the time is that i create a subscribe and unsubscribe method for a specific notification inside that class. For example these two methods subcribe or unsubscribe the object to the global keyboard notification:

@interface ObjectA : NSObject
-(void)subscribeToKeyboardNotifications;
-(void)unsubscribeToKeyboardNotifications;
@end

Inside those methods i use a private property which is set to true or false depending on the subscription state like so:

@interface ObjectA()
@property (nonatomic,assign) BOOL subscribedToKeyboardNotification
@end

@implementation

-(void)subscribeToKeyboardNotifications {
    if (!self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardShow:) name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardHide:) name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = YES;
    }
}

-(void)unsubscribeToKeyboardNotifications {
    if (self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = NO;
    }
}
@end
Sebastian Boldt
  • 5,283
  • 9
  • 52
  • 64
0

In addition to Adam's answer I would like to suggest to use macro like this

#define SafeRemoveObserver(sender, observer, keyPath) \
@try{\
   [sender removeObserver:observer forKeyPath:keyPath];\
}@catch(id anException){\
}

example of usage

- (void)dealloc {
    SafeRemoveObserver(someObject, self, somePath);
}
wattson
  • 81
  • 1
  • 8
0

Long Story Short

Take NSObject category from here and use it like this:

if ([observable tdw_hasObserver:observer forKeyPath:@"key.path" context:nil error:nil]) {
    [observable removeObserver:observer forKeyPath:@"key.path"];
} else {
    // go on your merry way
}

Public API approach

For many Foundation classes, which don't have any other observers but yours, you can just check observationInfo property value. The property returns a null pointer when the object doesn't have any observers or an opaque void * pointer otherwise:

if (observable.observationInfo) {
    [observable removeObserver:observer forKeyPath:@"key.path"];
} else {
    // go on your merry way
}

This, however will not work for most of UIKit classes and scenarios where you yourself use more than one observer.


Private API approach

If you don't mind messing with private API, any object can provide you with an opaque pointed to its observers data with use of NSObject's observationInfo property. Under the hood the pointer holds an object which in turn holds an array of so-called observances - special data structure to describe a single subscription (every invocation of -[NSObject addObserver:forKeyPath:options:context:] creates a new instance in the array, even if all arguments are the same). An observance memory layout (at the time of writing this answer at least) looks something like this:

@interface NSKeyValueObservance: NSObject {
    id _observer;
    NSKeyValueProperty *_property;
    void *_context;
    id originalObservable;
}

The combination of these variables is the information you are looking for. Of course you cannot reliably extract this data, because it's private API, however the following contract keeps working since ages ago:

  1. observationInfo should have an NSArray ivar which holds observances data;
  2. _observer, _property and _context ivars names are as is;
  3. _context and _observer can be compared with the desired data by pointer addresses;
  4. _property ivar has _keyPath of type NSString to compare with the desired key-path;

With that in mind you can extend NSObject with a category and implement a convenient method which checks whether certain combination is present or not.

I don't mind sharing my own implementation but it's a little too much to fit in a single SO answer. You can check it out in my gists page. Be advised, that this implementation performs ivars lookup by name and (sometimes) types. It is somewhat safer than just directly using ivar offsets, but still very fragile.

Here is a simple example of how to use it:

NSObject *observable = [NSObject new];
NSObject *observer = [NSObject new];
void *observerContext = &observerContext;

[observable addObserver:observer forKeyPath:@"observationInfo" options:NSKeyValueObservingOptionNew context:observerContext];
[observable addObserver:observer forKeyPath:@"observationInfo" options:NSKeyValueObservingOptionNew context:nil];
[observable addObserver:observer forKeyPath:@"observationInfo" options:NSKeyValueObservingOptionNew context:observerContext];

// Removes only 2 observances (subscriptions) where all parts matches (context, keyPath and observer instance)
while ([observable tdw_hasObserver:observer forKeyPath:@"observationInfo" context:observerContext error:nil]) {
    [observable removeObserver:observer forKeyPath:@"observationInfo" context:observerContext];
}

Two-Sentence documentation

If context and/or keyPath arguments are nil, the implementation looks for subscriptions with any context and/or keyPath.

In case of any (expected) error, the method returns NO and writes error details to the error object, if corresponding pointer is provided. Expected errors include some minor changes in the private API (but far from any).

The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49