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:
observationInfo
should have an NSArray
ivar which holds observances data;
_observer
, _property
and _context
ivars names are as is;
_context
and _observer
can be compared with the desired data by pointer addresses;
_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).