How can i tell if an object has a key value observer attached

58,788

Solution 1

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
}

Solution 2

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.

Solution 3

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.

Solution 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];
    }
}

Solution 5

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

Share:
58,788
Aran Mulholland
Author by

Aran Mulholland

Full stack, Javascript, HTML, .NET, .NET Core, ASP.NET MVC, Project Coding Infrastructure, iOS, neo4j development, analogue synthesis, toilet photographer.

Updated on November 03, 2020

Comments

  • Aran Mulholland
    Aran Mulholland over 3 years

    if you tell an objective c object to removeObservers: for a key path and that key path has not been registered, it cracks the sads. like -

    'Cannot remove an observer for the key path "theKeyPath" from 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
    }
    
  • Robert
    Robert over 12 years
    1+ Good answer, worked for me and I agree with your rant before it was edited.
  • Ben Gotow
    Ben Gotow over 12 years
    upvoted for deleted rant that I would most likely agree with.
  • bandejapaisa
    bandejapaisa over 12 years
    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
    bandejapaisa over 12 years
    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.
  • Nico
    Nico over 12 years
    @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.
  • Eimantas
    Eimantas about 12 years
    Do you happen to know how I can retrieve a specific observer? objectAtIndex: doesn't yield desired result .)
  • João Nunes
    João Nunes over 11 years
    Isn't here any other elegant solution? this one takes at least 2ms per usage... imagine it in a tableviewcell
  • Glenn Maynard
    Glenn Maynard over 11 years
    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
    Glenn Maynard over 11 years
    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.
  • Nico
    Nico over 11 years
    @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.
  • Glenn Maynard
    Glenn Maynard over 11 years
    Nothing in the question talks about removing other code's observers.
  • Nico
    Nico over 11 years
    @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.
  • neeraj
    neeraj about 11 years
    @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?
  • Nikolai Ruhe
    Nikolai Ruhe over 10 years
    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.
  • ma11hew28
    ma11hew28 about 10 years
    @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
    ma11hew28 about 10 years
    @PeterHosey what about automatic reference counting (ARC)? Don't you use it? What if we had ARC for KVO? Less code. More profit.
  • Nico
    Nico about 10 years
    @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.
  • Eimantas
    Eimantas about 10 years
    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 .)
  • brandonscript
    brandonscript almost 10 years
    @MattDiPasquale's answer is significantly better.. Not only does this answer destroy the observer, but it's very slow.
  • Yup.
    Yup. over 9 years
    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')
  • Vassilis
    Vassilis about 9 years
    It shouldn't crash anyway. It just leads to additional conditional checks.
  • nefarianblack
    nefarianblack almost 9 years
    Source: NSKeyValueObserving.h
  • Vipulk617
    Vipulk617 over 8 years
    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.
  • jailani
    jailani over 8 years
    We can use try and catch for this special case. This what I understood from nshipster.com/key-value-observing
  • Vipulk617
    Vipulk617 over 8 years
    I am facing same issue to remove observer in swift. How to resolve this issue ?
  • FredericK
    FredericK over 8 years
    Accordingly to Apple docs : observationInfo returns a pointer that identifies information about all of the observers that are registered with the receiver.
  • Martin Marconcini
    Martin Marconcini about 8 years
    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.
  • funct7
    funct7 about 8 years
    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
    funct7 about 8 years
    I totally agree. I hate the smart-alecks. Tunnel vision. Seriously
  • Nico
    Nico about 8 years
    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.
  • funct7
    funct7 about 8 years
    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.
  • MrTJ
    MrTJ almost 8 years
    @PeterHosey what about the use case outlined in stackoverflow.com/questions/38526237/… ?
  • Andre Simon
    Andre Simon almost 8 years
    great idea if you don't always are observing.
  • Ky -
    Ky - almost 8 years
    Use case: a stateless program.
  • Ky -
    Ky - almost 8 years
    I'm with @vipulk617; I can't find a safe way to do this in Swift.
  • Ky -
    Ky - almost 8 years
    This was better-said in @mattdipasquale's answer
  • shrutim
    shrutim over 7 years
    If this happens in a multi threaded world, you need to make sure your array is ThreadSafe
  • Aran Mulholland
    Aran Mulholland over 7 years
    How crazy is it that it throws an exception? Why doesn't it just do nothing if nothing is attaches?
  • pablobart
    pablobart about 7 years
    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. clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptio‌​ns If you don't consider this you could end up with memory leaks and memory increases.
  • Joshua Hart
    Joshua Hart almost 7 years
    [self removeObserver:nil forKeyPath:@""]; needs to go before: [super viewWillDisappear:animated];
  • quarezz
    quarezz almost 7 years
    @JoshuaHart why?
  • Joshua Hart
    Joshua Hart almost 7 years
    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]; }
  • quarezz
    quarezz almost 7 years
    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.
  • Mehul Thakkar
    Mehul Thakkar over 6 years
    Any solution for Swift?
  • Will Von Ullrich
    Will Von Ullrich over 5 years
    plus 1 for a comically dead end but still somewhat helpful answer
  • atulkhatri
    atulkhatri almost 5 years
    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.