Change notification for self in NSMutableDictionary subclass - objective-c

I've subclassed NSMutableDictioary following mainly this great blog post.
Now in that subclass the dictionary itself subscribes to all its values in order to detect any changes. This bit works just fine and value changes are observed internally in that subclass.
However value changes should then be propagated to observers of the dictionary, i.e. observers of the dictionary (subclass) are supposed to get notified that the dictionary has changed.
Except I cannot figure out how to trigger that KVO notification - using
[self didChangeValueForKey:#"self"];
doesn't seem to have any effect so I'm kinda stuck.
How can an instance of the NSMutableDictionary subclass initiate a change notification for itself?

As already hinted by Ken KVO allows you to observe properties - not an object per se!
The Key-Value Observing Programming Guide states, that
Key-value observing provides a mechanism that allows objects to be notified of changes to specific properties of other objects.
In other words: self can never be observed.

Related

Using Key Value Observing to detect when an object gets deallocated

How can I find out when an object is being released? I am listening for kvo changes, but the object get's deallocated before the retain count goes to 0, and I get the following warning:
An instance 0x16562be0 of class MyViewController was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
Basically what I'm trying to do is to detect when the model is dismissed. I can't use a Delegate, because the viewControllers being presented are dynamic, and my mainViewController has no knowledge about them other than the fact that they are subclasses of UIViewController.
[anotherViewController addObserver:self forKeyPath:#"retainCount" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Here check for the changes and see of the new value is 0 or not
}
I also tried listening for the superView of the viewController being changed to nil
[anotherViewController.view addObserver:self forKeyPath:#"superView" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:nil];
You can only do Key-Value Observing on keys for which the object supports it. What you want to do here is simply not possible — an object's observers are all supposed to be gone by the time it gets to dealloc. You will need to structure your application such that either this object is kept around as long as it is needed or it actively tells interested parties before it goes away.
And looking at an object's retainCount is just never a good idea. As far as it is useful, it is only useful for debugging — and even then there are much better and more reliable tools. The result of retainCount is simply misleading, and it does not work the way most people expect. Watching for it to be 0 is an exercise in futility, because no object can exist with a retain count of 0 — when an object with a retain count of 1 is released, it gets deallocated, and then you are not allowed to message it anymore. (In fact, the framework literally has no way of representing a 0 retain count because it's an unreachable state.)
UPDATE
As of late 2017 (iOS 11, macOS 10.13), when an object is deallocated, it automatically unregisters any remaining observers. From the Foundation release notes for that year:
Relaxed Key-Value Observing Unregistration Requirements
Prior to 10.13, KVO would throw an exception if any observers were
still registered after an autonotifying object's -dealloc finished
running. Additionally, if all observers were removed, but some were
removed from another thread during dealloc, the exception would
incorrectly still be thrown. This requirement has been relaxed in
10.13, subject to two conditions:
The object must be using KVO autonotifying, rather than manually
calling -will and -didChangeValueForKey: (i.e. it should not return NO
from +automaticallyNotifiesObserversForKey:)
The object must not
override the (private) accessors for internal KVO state
If all of these are true, any remaining observers after -dealloc
returns will be cleaned up by KVO; this is also somewhat more
efficient than repeatedly calling -removeObserver methods.
And as of late 2020 (iOS 14, macOS 10.16), KVO is even more careful when an object still has observers during deallocation:
Key-Value Observing
New Features in iOS & iPadOS 14 beta 5
Key-Value Observation removal facilities now employ deterministic
bookkeeping methods. Cases that would have produced hard-to-diagnose
crashes, especially those where KVO signals problems accessing
deallocated observer pointers or observers associated with incorrect
objects, now produce an exception pinpointing which observed object
needs a missed removeObserver(_:) call, and by which observers. This
exception was previously thrown as ‘best effort’ when KVO could detect
the problem; the new deterministic bookkeeping allows it to be thrown
for all cases where removeObserver(_:) is needed.
The improved determinism also allows improved Swift API handling.
Instances of NSKeyValueObservation, produced by the Swift
NSObject.observe(_:changeHandler:) method, take advantage of
integration with this bookkeeping so they now invalidate automatically
when the observed object is released, regardless of how the object
implements its KVO behavior. This applies to all usage of this API in
macOS 11 Big Sur beta, including on processes built with previous
versions of the SDK, and eliminates certain classes of crashes that
sometimes required using the legacy API instead. (65051563)
ORIGINAL
There are a few problems here.
One problem is that you asked the wrong question. You meant to ask “How do I deregister my observer at the right time, before the target is deallocated?” Instead, you mentioned retainCount, which tends to provoke people into berating you about using retainCount instead of helping you do what you're trying to do, which is deregister your observer at the right time.
Another problem is that your view controller doesn't own its model (meaning it doesn't have a strong reference to the model). Usually you want your view controller to own its model, to prevent exactly this sort of problem. While your view controller exists, it needs a model to operate on, so it should own the model. When the view controller is being deallocated, it should stop observing its model and release it. (If you're using ARC, it will release the model automatically at the end of dealloc). You might also choose to deregister in your viewWillDisappear: method, if your view controller goes on and off of the screen repeatedly.
Note that an object can be owned by multiple other objects simultaneously. If you have several view controllers operating on the same model, they should all own the model, meaning that they should all have strong references to the model.
A third problem is that you're (probably) using KVO directly. The built-in KVO API is not very pleasant to use. Take a look at MAKVONotificationCenter. This KVO wrapper automatically unregisters an observer when the observer or the target is deallocated.
if you are interested in getting notified when an object gets deallocated you could send a notification in dealloc, but don't reference the object getting dealloc'ed.
for instance
[[NSNotificationCenter defaultCenter] postNotificationName:#"myclass_dealloced" \
object:[NSValue valueWithPointer:self]];
but you wouldn't ever want to dereference that pointer...
use this only for debugging and testing.
Trying to automatically de-register observers during dealloc is too late.
When dealloc is called, the state of the object graph is undefined. Specifically, order of deallocation is typically not guaranteed and may often change in light of asynchronous processes and/or autorelease.
While the graph the deallocating object strongly references should be coherent, that'll quickly change as the object is deallocated.
The same holds true for the observer of the object being deallocated; as deallocation of an object graph happens, the observed objects state may likely change. As it changes, it may cause observers to fire while the object graph is in the inconsistent, being deallocateed, state.
You really need to concretely separate deallocation from observation logic.
That is, when your controller is dismissed from screen, it should actively dismiss the model layer, including tearing down any observers (or notifying any observers that the model layer is about to go away).
Your observers need to de-register their notifications at the same time they let go of the object.
For example, if your objects are registering notifications on one of their properties, de-register all the notifications before the property is changed or set to nil.
There never should be "hanging" notification registrations to objects that have been simply lost track of. How can you deregister your notifications if you lose track of the object?
just do what KVO says. Observe, act accordingly and signal the Key manually when you need to. That way you can of course know when an object gets deallocated.
When you removeObserver from the Object and it is already deallocated then the method call is acting on nil which does no harm or your observing object holds still a reference and in such case you can still act accordingly.
With ARC this is not a problem and one of the great benefits.
Test it yourself..
// public header.
#interface ObjectToBeObserved : NSObject
#end
// declare in private header
// because you dont want to allow triggering from outside
#interface ObjectToBeObserved : NSObject
// use some artificial property to make it easy signalling manually.
#property (nonatomic) BOOL willDealloc;
#end
#implementation ObjectToBeObserved
-(void)dealloc {
[self willChangeValueForKey:#"willDealloc"];
[self didChangeValueForKey:#"willDealloc"];
}
#end
In your Observer side you just do classic KVO design pattern..
void* objectDeallocatedContext = & objectDeallocatedContext;
#implementation ObservingObject {
// easy to see you could even make a protocol out of the design pattern
// that way you could guarantee your delegate has such property to observe
__weak ObjectToBeObserved *delegate;
}
-(instancetype)initWithObservableDelegate:(ObjectToBeObserved*)observable {
if (!(self=[super init])) return nil;
delegate = observable;
// see i use observe old value here..
if (delegate!=nil)
[delegate addObserver:self forKeyPath:#"willDealloc" options:(NSKeyValueObservingOptionOld) context:objectDeallocatedContext];
return self;
}
-(void)dealloc {
if (delegate!=nil)
[delegate removeObserver:self forKeyPath:#"willDealloc" context: objectDeallocatedContext];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context==objectDeallocatedContext) {
NSLog(#"the observed object deallocated");
// in theory you hold still a weak reference here
// which should be nil after this KVO signal arrived.
// the object in the signal therefore might not be valid anymore,
// which is what you want when observing deallocation.
}
}
#end
KVO is a signal pattern, not a way to know if a signalling object is still valid. But when the object is gone it will not signal anything, those when you can receive the signal you are just fine. Because i choose to watch the NSKeyValueObservingOptionOld value with a void* context, it gets even signaled before the objects artificial "willDealloc" property is set (well, not even set). The KVO can arrive without a valid object but has still a context to compare to. You just need the ping

KVO with NSPrivateQueueConcurrencyType

I would like to determine if an attribute changed in core data and update my UI. The attribute may change as the result of a background fetch running in a managed object context of type NSPrivateQueueConcurrencyType.
I added a listener: [myCoreDataEntity addObserver:self forKeyPath:myCoreDataAttribute options:NSKeyValueObservingOptionNew context:nil]
But the event never fires. Any idea why? The object is changed in another context - could this be the reason? (When a save: is done on the parent context, nothing still fires).
I can use manual KVO, but since the object has not been saved yet to the parent context, refreshing the UI does not work since it is point at the context in NSMainQueueConcurrencyType when the object was changed in a context associated with NSPrivateQueueConcurrencyType
You're not getting notifications because you're observing the wrong object. The NSEntityDescription never changes at run time. It's a representation of the way the entity was defined in the data model. Instances of NSManagedObject that use the entity description can and do change at run time, though. If you want to know whether an attribute changed on a specific managed object, you need to observe that specific object.
If you need to get notified any time any managed object changes the value for that attribute, your best option is to write a custom setter for that attribute and handle it there. You might also find NSManagedObjectContextObjectsDidChangeNotification useful, but that will fire for any attribute change.
Assuming that myCoreDataEntity in your example is a managed object, the issue is that automatic external change notifications are [disabled by Core Data for managed objects] for modeled properties1:
NSManagedObject disables automatic key-value observing (KVO) change notifications for modeled properties, and the primitive accessor methods do not invoke the access and change notification methods. For unmodeled properties, on OS X v10.4 Core Data also disables automatic KVO; on OS X v10.5 and later, Core Data adopts to NSObject’s behavior.
You can turn them on for specific properties, or for all properties in your managed object subclass or in a category on the managed object subclass:
Single property:
- (BOOL) automaticallyNotifiesObserversFoMyCoreDataAttribute {
return YES;
}
All properties (Not recommended):
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
return YES;
}
The reason it does not send out automatic change notifications is primarily performance. Automatic change notifications do add some overhead, though on recent hardware it's fairly minor, even when working with thousands of objects. As always, profile to see what works for you.

KVO produces error/not receiving notification

I have a class (call it classA) that contains a property named info (a model class, containing lots of info), wich in turn contains a property named name (a string). I want another class (classB) to receive a KVO notification when the string name changes in classA.
This is what I'm doing now on classB:
[classA addObserver: self forKeyPath: #"info.name" options: 0 context: nil];
There are two ways the value name changes on classA: when it is set directly like classA.info.name = ... and when info is set like classA.info = ...
When name is changed directly KVO works perfectly. However, when the info property is set and name changes indirectly, I get this error:
Cannot update for observer <classB> for the key path "info.name" from <classA>, most likely because the value for the key "info" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the classA class.
What should I change on classA to make this work?
The cause of this issue comes from the fact that you are implementing the setter -setInfo: for info and calling willChangeValueForKey: | didChangeValueForKey: inside it.
I've seen many instances in which it is believed that the will|didChange... calls need to be implemented for the KVO notification to be fired. This is true only when the setter is not explicitly called. When you do call the setter, the KVO mechanism takes care of firing the notification.
Most of the times leaving these calls in the setter is harmless and just causes extra notifications, but as seen in this case, it does cause a problem when updating a keypath -- as opposed to a key.
In short, if you do implement a setter, do not call will|didChangeValueForKey: inside of it.
Options cannot be 0, I think.
Its an old API, back before Apple got diligent about API design, and you have to give it something.

Providing your own setter for a CoreData property / attribute

I have an entity with several properties, one of them called lastModificationDate. Whenever any of the object's properties is set, I'd like to update the lastModificationDate.
If I were not using Core Data, I would just provide my own setter for the properties and update lastModificationDate. However, I'm not sure if I should mess around with CoreData's properties.
What's the best way to do this?
Overriding the setters can easily be done, you have to make sure you fire the right notifications for everything else to work (including KVO).
- (void) setThing:(NSObject *)myThing {
self.lastUpdateDate = [NSDate date];
[self willChangeValueForKey:#"thing"];
[self setPrimitiveThing:myThing];
[self didChangeValueForKey:#"thing"];
}
This being said, if all you need to do is the code I showed (essentially setting the value and updating the last update date), you are much better off using Key-Value Observing and reacting to the notifications. It's easier and cleaner.
You shouldn't override property mutators (setters) if you're working with an NSManagedObject subclass because those implementations are provided at runtime (hence #dynamic instead of #synthesize). You could if you really wanted to, but it's messier and there's no reason to. Use Key Value Observing (KVO) instead. It'll let you know when a value is changed.
Apple's KVO documentation is great: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i

Cocoa Touch Question. Using KVO in a touch sequence context

I would like to use KVO in the following context:
1) In touchesBegan:withEvent: I alloc/init an instance of an object that I then observe via KVO
My intent is to observe varous behaviors of the object throughout its life time.
2) In touchesEnded:withEvent: I assign this instance to an NSMutableArray and release the instance reference since NSMutableArray now retains it. I also must remove the oberver of the instance via removeObserver:forKeyPath:
This is problematic because I now have lost all observation unless I add the observe back again to the array element which smells bad.
Is there a way to have the observer remain attached to the object regardless of who owns it?
Thanks,
Doug
In Objective-C, you don't "own" an object, you merely have a claim on it. You don't need to release the instance just because the NSMutableArray retains it -- you can both have a claim on it. When you've finished with the object, remove yourself as an observer and release the object. When you've finished with the NSMutableArray, release that. This way, everything takes care of itself.