Perfect KVO here includes two parts: add observer correctly and remove observer correctly.
The story:
I use one UITableViewCell(cell) to display one NSManagedObject(object).
Each object has some dynamic properties that need observing by its cell.
Not all objects have the same set of observed properties. I add key path observers selectively like this:
if (object.thumbnail_pic_url)
[object addObserver:cell forKeyPath:#"thumbnail_picture" options:0 context:NULL];
Object could be deleted. I must remove observers when object is deleted. The database is very large and complex so I definitely don't want to register all cells to receive moc notifications like NSManagedObjectContextObjectsDidChangeNotification. But I can accept to add a cell ivar in object if I have to, even though it goes agains good Modle-View-Controller design pattern.
The problem: How can I correctly remove the observer(cell) for all the registered key paths from an object when it is deleted?
In fact, it is a big problem that can be divided into two small problems:
Where is the best place to put the observer removing code?
How do I determine which key paths to unregister? I can't query its properties after an object is deleted — it will cause unfulfillable faults, so I can't write code like this:
if (object.thumbnail_pic_url)
[object removeObserver:cell forKeyPath:#"thumbnail_picture"];
and I can't either blindly remove observer for unregistered key path — exceptions(Cannot remove an observer for the key path "thumbnail_picture" from because it is not registered as an observer.) will be thrown up.
an0,
There is an NSManagedObject method just for doing deletion timed functions: -prepareForDeletion.
Its documentation claims: "You can implement this method to perform any operations required before the object is deleted, such as custom propagation before relationships are torn down, or reconfiguration of objects using key-value observing."
You could also look at using: -willTurnIntoFault and -didTurnIntoFault. But I think you'll be happier using -prepareForDeletion.
Andrew
P.S. This method is documented in the class reference. I respectfully suggest that you save time by reading the documentation.
The main problem implementing KVO here is that you don't know when the object is getting deleted , at least not outside the NSManagedObject subclass, What you could really do is create a generic delegate on a subclass of NSManagedObject and override its didChangeValueForKey: method
// DataObservingManagedObject.h
#import <Foundation/Foundation.h>
#import <MMRecord/MMRecord.h>
#protocol DataObservingDelegate <NSObject>
-(void)valueChangedForKey:(NSString*)key andValue:(id)value;
#end
#interface DataObservingManagedObject : NSManagedObject
#property(nonatomic,weak)id<UserStatusDelegate> changeDelegate;
#end
//DateObservingManagedObject.m
#import "DateObservingManagedObject.h"
#implementation DateObservingManagedObject
#synthesize changeDelegate=_changeDelegate;
-(void)didChangeValueForKey:(NSString *)key{
[self.changeDelegate valueChangedForKey:key andValue:[self valueForKey:key]];
}
#end
I believe the mistake is in step 1. Cells should be designed to display different objects. Cells are simply views with labels that can show anything. Tables are optimised to reuse the same cells for different data objects. In your VC create a configureCellWithEvent, configureCellWithVenue methods etc. then you can dequeue a cell of generic identifier and pass it to these methods. Then you likely won’t even have the issue of when to add remove observers because cells shouldn't have object observers.
Related
I'll try to summarize this as succinctly as possible:
I have three classes: a ViewController, a ViewModel, and a DataSource.
The ViewController creates both the ViewModel and DataSource. It then proceeds to "configure" the DataSource with a NSMutableArray that was initially created by and is owned by the ViewModel.
I'm aware that by implementing the appropriate indexed accessor methods I can make the NSMutableArray property KVO compliant so that my ViewModel can observe its changes.
However, what I want to do is observe the changes from my DataSource.
When it is configured the DataSource, "sets" the NSMutableArray to its own "objects" property (weak, strong, copy???). Whenever the NSMutableArray is updated via the ViewModel (which does the fetching from the server), my ViewModel is made aware of the changes. But, I can't seem to be able to observe these changes from the DataSource.
Any suggestions?
(Also, not a priority, but does anyone know of a way to do this "reactively" with RAC?).
Yes, it's possible to observe changes to an array property of one class from another class. I've done it. It's been a long time, and I don't have the code at hand, so I can't give you specifics, but it is indeed possible.
It's fragile, since if the listening object gets deallocated without removing itself as a KVO observer, you crash.
This sort of KVO observation seems like bad mojo to me. It creates fairly tight coupling between your classes, and makes the observing class dependent on implementation details of the observed class.
If I were to do this again I would probably use KVO within the class that owns the array, and then broadcast a notification, use a callback, or use some other method to notify the interested external object about the change that does not have such tight coupling.
In a project I have a class say "A", there are many other class that are observing class "A"'s property values.
Sometimes the class "A" instance gets deallocated and observees fails resulting in crash!
Is there any way to remove all the observers from class "A"? Something like this:
-(void) dealloc{
[remove allObservers forKey:#"theKey"];
}
In short, sadly no. KVO is made so that it gets you in the end, unfortunately.
I've struggled with this thing before, and I found the following two solutions:
Use a proxy method to register for observation in your observee, which will maintain a list of weak references to observers.
Ideally, you'd want a proxy method for removing observers as well, so that your list is updated accordingly (although, since they are weak references in your list, it wouldn't hurt if some observer removed itself using standard KVO instead of your proxy method, and then deallocd itself).
In case your observee gets deallocated, it should inform all observers (using a protocol), or just remove them outright itself. For the last one, using exceptions might come in handy as well (I am aware that exceptions are evil in Obj-C, but what to do):
#try
{
[self removeObserver:observee forKeyPath:#"path"];
}
#catch (NSException * __unused exception) {}
Use some abstraction from KVO. There are a couple of projects that come to mind such as RZDataBinding and MAKVONotificationCenter (despite it's name, it actually pertains to KVO)
You should keep "A" class alive until there are other objects observing its property values. Maybe it gets deallocated because you're not correctly handling its reference.
You should check if "A" needs a 'strong' reference. When you don't need "A" anymore (i.e.: you're popping a view controller, you are refreshing a table, you're clearing a scrollview), you should also remove any observer attached to it (and be able to do it).
I have a shared singleton that contains all relevant information on the current user and its session, through the object [IWSession session] and [IWSession session].currentUser.
The current user (which actually refers to the one logged in the application) might have some of its properties updated frequently through webservice calls (triggered by iBeacon, triggered by a change in its location, etc).
This implies to update the GUI accordingly at different places in the app, let's say 5 or 6 class instances.
What's the proper way to update information displayed in the app as soon as any property is updated ?
I thought about
1) Adding a KVO on the [IWSession session].currentUser on himself and for all properties regarding the following link
Key Value Observing - how to observe all the properties of an object?
2) The KVO would then trigger a
[[NSNotificationCenter defaultCenter] postNotificationName:#"userUpdated" object:nil];
and all classes which need their layout to be updated would listen to that notification.
Is it a good approach ?
Any other suggestion ?
If you use Notifications then memory will not be released for all the objects in which notification is posted. Because if you use notifications then reference to the object is stored in the heap. So I don't this it would be a good idea to use Notifications.
I recently have been using a weak-observer list using hashtables
mObservers = [NSHashTable weakObjectsHashTable];
with semi-delegation messages, for example:
#protocol UserSessionObserver <NSObject>
- (void) userSession:(id)session didUpdateUser:(id)userProfile;
#end
So, any object that would be interested in changes to userProfile or userSession can simply add itself as an ad-hoc observer to the shared userSession. Because it is a weak entry, the object will be removed automatically from the observer table on dealloc.
Trick is to create the correct addObserver message:
- (void) addObserver:(__weak id<UserSessionObserver>)observer;
While this requires you to write your own observer logic, it also means:
1. No memory management issues with strong retain cycles,
2. Simplified and to the point observer messaging (the system notifications and KVO implementation is so generic, it is bordering on incomprehensible imo, but also adds several layers of logic that merely slows down processing).
An example implementation: Weak Observer example
I have placed all of my NSManagedObject's custom logic in a category, so that I can regenerate the standard classes from my model if an when it changes.
One such piece of logic I require is a custom setter on one of the object's properties:
- (void) setName:(NSString *)name
{
[self willChangeValueForKey:#"name"];
[self setPrimitiveValue:name forKey:#"name"];
[self didChangeValueForKey:#"name"];
NSLog(#"name was changed");//for example
}
I have placed this in the category, which in this case is Item+Custom.m
My question:
Why is it that, whenever I set the name of an Item, it is not necessary to import Item+Custom.m? The log statement above still fires.
Just curious how the class sending the message doesn't need to know about the category for the logic to still fire?
And (perhaps a separate issue) what would happen if I added the same custom setter, with a different logging statement, to a second category on the same object?
When a program is loaded, all category methods are made known to the runtime. So if you declare a -[Item setName:] method, then Core Data will not create this method at runtime anymore.
You need not import anything because name is already declared as a #dynamic property in the Xcode generated managed object subclass files.
If two categories declare the same method, or if the name of a method declared in a category is the same as a method in the original class, the behavior is undefined, see Avoid Category Method Name Clashes in "Programming with Objective-C".
I used this 'tutorial' to bind my array called 'collection' to a NSTableview on my interface:
http://www.cocoadev.com/index.pl?NSArrayController
The interfacebuilder stuff isn't that hard. It becomes difficult when I try to actually show the data in my array into the view.
in my .h file:
#interface MyDocument : NSDocument
{
NSMutableArray *collection;
//other variables
}
and in my .m file:
#implementation MyDocument
#synthesize collection;
//quite some functions
inside one function (that works):
[collection addObject:fileName];
//some other functions
inside the init function:
collection = [[NSMutableArray alloc] init];
Now I guess the array is bound well to the interface and the tableview inside it, but ofcourse the tableview and its columns need to be filled in a specific way. Right now nothing shows after adding an item. with collection addObject:fileName function
Should I create a sub-Array as one item, filled with fields? And how should I bind these values/fields to the specific columns. (the fields are 'artist', 'title', etc)
I have already bound every column in Interface Builder to Array Controller with Controller key 'arrangedObjects' and Model Key Path 'artist','title',etc.
Please keep the explanation simple since I'm slowly starting to think I will never get this Array Controller thing... Objective-C doesn't seem that hard, but the binding which it needs is what I just don't get. Apple's examples are not sufficient to newbies
Typically to populate your data you'd use a dictionary (the key would be the keyPath, and object the data) for each row, or even better, create a class to represent the data and create a new instance for each row. Bindings can be a little tricky at first (if you're new to Cocoa get used to the data source methods first), but have a look at this tutorial and the examples here. Both contain samples you can download and examine exactly how the bindings is set up in Interface Builder.
Just mutating the array doesn't tell anything that the array has changed. You need to send KVO notifications for the mutation.
The right way to do this is to implement accessor methods for the property, then call your own accessors. In this case, you'll want to implement insertObjectInCollection:atIndex: and pass the length of the array as the index ([self insertObjectIntoCollection:fileName atIndex:[self countOfCollection], after also implementing countOfCollection).
When you implement accessors, then when an object binds to the property, Cocoa will wrap the accessors in KVO magic that will send the appropriate notifications for the mutation.