What is the best way to respond to data changes when property setters are called. For example, if I have a property called data, how can I react when [object setData:newData] is called and still use the synthesised setter. Instinctively, I would override the synthesised setter like so:
- (void)setData:(DataObject *)newData {
// defer to synthesised setter
[super setData:newData];
// react to new data
...
}
...but of course this doesn't make sense - I can't use super like this. So what is the best way to handle this situation? Should I be using KVO? Or something else?
There are a few different ways to do this, depending on how much control you want. One way to do it is to observe your own property:
[self addObserver:self forKeyPath:#"data" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)path ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if(object == self && [path isEqualToString:#"data"]) {
//handle change here
} else [super observeValueForKeyPath:path ofObject:object change:change context:context];
}
Make sure you remove yourself as an observer in your dealloc or finalize method, if not before.
Another way would be to override -didChangeValueForKey:. However, this method may not be called if there are no observers on the object.
- (void)didChangeValueForKey:(NSString *)key {
[super didChangeValueForKey:key];
if([key isEqualToString:#"data"]) {
//handle change here
}
}
#synthesize creates default accessors for easy use. In case some special action is needed then it is always possible to write own accessors instead of using #synthesize. The setter and getter are not inherited from base class, they are created by the #synthesize directive. So you don't need to (neither you can) call super setData: (unless you really have created super class that support that).
Just ensure that you are managing memory correctly. Memory Management Programming Guide contains examples on how to manage memory for different types of memory policy (retain or assign or copy).
From this SO answer.
You can define a synthesized "private" property, (put this in your .m file)
#interface ClassName ()
// Declared properties in order to use compiler-generated getters and setters
#property (nonatomic, strong <or whatever>) NSObject *privateSomeObject;
#end
and then manually define a getter and setter in the "public" part of ClassName (.h and #implementation part) like this,
- (void) setSomeObject:(NSObject *)someObject {
self.privateSomeObject = someObject;
// ... Additional custom code ...
}
- (NSArray *) someObject {
return self.privateSomeObject;
}
You can now access the someObject "property" as usual, e.g. object.someObject. You also get the advantage of automatically generated retain/release/copy, compatibility with ARC and almost lose no thread-safety.
Related
Can anyone suggest a good pattern for implementing a dirty flag on Realm objects? Specifically, I would like every subclass of Realm Object to expose an isDirty flag that gets set whenever an instance of the class is modified and is cleared whenever the instance is written to the cloud (not the Realm). I'm working in Objective-C.
Possible solutions I can think of include the following:
Write a custom setter for every property of every objects. Set isDirty within each of those setters. Not very desirable.
Use KVO in some way. Two problems with this approach: (a) I don't fully understand how to implement this approach, and (b) Realm doesn't support KVO for managed objects (which are exactly the objects I need it for).
Use Realm notifications. Again, I don't have experience with these, and I'm not sure how to use them for this purpose.
Short of simply having a non-managed isDirty property that you manually set after performing each write transaction, KVO would be the best way to go.
Setting custom setters would indeed be incredibly messy. You'd have to have a separate one for each property you wanted to track.
Realm notifications would only work if you were tracking a set of objects and wanted to be alerted if any were changed (using collection notifications) or if anything in the Realm changed.
With KVO, you could potentially get your object subclass itself to add observers to all of its properties, which are then channeled to one method whenever any of them change, this could then be used to mark the isDirty property.
#interface MyObject: RLMObject
#property NSString *name;
#property NSInteger age;
#property BOOL isDirty;
- (void)startObserving;
- (void)stopObserving;
#end
#implementation MyObject
- (void)startObserving
{
NSArray *properties = self.objectSchema.properties;
for (RLMProperty *property in properties) {
[self addObserver:self forKeyPath:property.name options:NSKeyValueObservingOptionNew context:nil];
}
}
- (void)stopObserving
{
NSArray *properties = self.objectSchema.properties;
for (RLMProperty *property in properties) {
[self removeObserver:self forKeyPath:property.name];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
{
self.isDirty = YES;
}
+ (NSArray *)ignoredProperties {
return #[#"isDirty"];
}
#end
Obviously you'd want to do more checking in here than I've done (to make sure isDirty truly needs to be set), but this should give you an idea.
There's no real way to automatically know when a managed Realm object has been created, so it would be best for you to manually start and stop observing as you need it.
I'd like to be notified, when the count, ie. number of items in an NSArray changes..
Of course I wouldn't need this, if I was in control of addition and removal of objects into the array. But I am not, it happens unpredictably with regards to Business Process Model and depends on external factors.
Is there some simple elegant solution?
EDIT: I am correcting this to NSMutableArray of course..
You’ll need to use KVC. But how to go about doing it? After all, NSMutableArray is not Key-Value-Coding compliant for its mutation methods or contents changes. The answer is proxying –as subclassing NS[Mutable]Array is far too much of a hassle.
NSProxy is a great little class that you can use to intercept the messages sent to your array as though you were an NSMutableArray, then forward them on to some internal instance. Unfortunately, it is also not KVC compliant, as the guts of KVC live in NSObject. We’ll have to use that, then. A sample interface might look something like this:
#interface CFIKVCMutableArrayProxy : NSObject {
NSMutableArray *_innerArray;
}
- (NSUInteger)count;
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)addObject:(id)anObject;
- (void)removeLastObject;
- (void)insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
//…
#end
As you can see, we’re simulating an interface for NSMutableArray, which is necessary, as our proxy should implement everything as though it were an NSMutableArray. This also makes the implementation as simple as possible, as we can just forward the selectors on to our inner NSMutableArray pointer. For the sake of brevity, I’ll only implement two methods to show you what a general outline looks like:
#implementation CFIKVCMutableArrayProxy
//…
- (NSUInteger)count {
return _innerArray.count;
}
- (void)addObject:(id)anObject {
[self willChangeValueForKey:#"count"];
[_innerArray addObject:anObject];
[self didChangeValueForKey:#"count"];
}
- (void)removeLastObject {
[self willChangeValueForKey:#"count"];
[_innerArray removeLastObject];
[self didChangeValueForKey:#"count"];
}
#end
If you have no opportunities to wrap an array like this, then try to re-think your code. If an external dependency is forcing you into this kind of corner, try to remove it. It’s always a bad thing to work around your own tools.
To observe changes in a mutableArray one needs to use mutable proxy object given by
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key
which is KVO compliant, i.e. any change of proxy object sends will/did change notifications.
The following demo class shown the full implementation
#interface DemoClass : NSObject
#property (nonatomic) NSMutableArray *items;
- (void)addItemsObserver:(id)object;
- (void)removeItemsObserver:(id)object;
#end
#implementation DemoClass
- (NSMutableArray *)items;
{
return [self mutableArrayValueForKey:#"_items"];
}
- (void)addItemsObserver:(id)object
{
[self addObserver:object forKeyPath:#"_items.#count" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}
- (void)removeItemsObserver:(id)object
{
[self removeObserver:object forKeyPath:#"_items.#count" context:nil];
}
#end
#interface ObservingClass : NSObject
#property (nonatomic) DemoClass *demoObject;
#end
#implementation ObservingClass
- (instanstype)init
{
if (self = [super init]) {
_demoObject = [DemoClass new];
[_demoObject addItemsObserver:self];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(#"is called on demoObject.items.count change");
}
- (void)dealloc
{
[_demoObject removeItemsObserver:self];
}
#end
Now every time you add or remove an object in the items you'll see new log in console (observeValueForKeyPath is called).
Any direct change of auto-synthesised ivar _itemsarray will have no effect.
Also note that you strongly need to set the observer on _items.#count (observing items.#count is senseless).
Note that you needn't to init _items or self.items. It will be done behind the scene when you call items getter.
Every time you change the "array" items you will get new object _items with new address. But I can still find it via items proxy getter.
Is there any way to use the ObjC runtime library, or Cocoa, to be notified when an object is created, for example, after it returns from the init method?
I want to achieve this without modifying the object, or subclassing it (no subclass on NSObject, for example) and without method swizzling (I already know how to do that).
There is no sanctioned way to be notified when a method executes, unless it specifically notes that it returns a notification, or a pointer to some kind of callback, a block, etc. While swizzling may be one way of going about it, proxying is probably your best bet. Instead of messing with the selector for an entire class, you interpose yourself "as" the class by implementing all its properties and/or forwarding selectors to the target object. In this way, NSProxy and subclasses can be used as wrappers around normal objects, meaning you can respond to any kind of method that happens to be sent through your proxy before forwarding it on to the target. A simple proxy can be modeled after the sample below:
FOUNDATION_EXPORT NSString *const CFIProxyDidInitializeTargetNotification;
#interface CFIObjectProxy : NSProxy {
__strong Foo *_target;
}
- (id)init;
#property(nonatomic, readonly, retain) NSArray* bars;
#end
//...
#import "CFIObjectProxy.h"
NSString *const CFIProxyDidInitializeTargetNotification = #"CFIProxyDidInitializeTargetNotification";
#implementation CFIObjectProxy
- (id)init {
_target = [[Foo alloc]init];
[NSNotificationCenter.defaultCenter postNotificationName:CFIProxyDidInitializeTargetNotification object:nil];
return self;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:_target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_target methodSignatureForSelector:sel];
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [NSString stringWithFormat:#"<%#:%p> Proxy for Object: %#", NSStringFromClass(self.class), self, _target];
}
- (NSArray*)bars {
return [_target bars];
}
#end
Per default, the runtime doesn't record this. I think I'd use swizzling BUT as you don't want this... I think that CodaFi's idea of wrapping the object in a proxy is best ALTHOUGH this is only an option for allocations you manually do AFAICS
so if you want it to be truly transparent, swizzle after all I'd say
I need to observe union-typed properties on an Objective-C class using KVO, but it seems I have no luck with this. I did some experiments: everything works fine as long as I am using a C struct. As soon as I replace the struct with a union, automatic KVO doesn't work anymore (observeValueForKeyPath is not being called).
Here's my small test class:
AppDelegate.h:
#import <Cocoa/Cocoa.h>
typedef union {
float data[3];
struct {
float x,y,z;
};
} vec3union;
typedef struct {
float x,y,z;
} vec3struct;
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (assign) vec3struct vectorStructValue;
#property (assign) vec3union vectorUnionValue;
#end
AppDelegate.m:
#implementation AppDelegate
#synthesize vectorStructValue = _vectorStructValue;
#synthesize vectorUnionValue = _vectorUnionValue;
- (void)dealloc
{
[super dealloc];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self addObserver:self forKeyPath:#"vectorStructValue" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self forKeyPath:#"vectorUnionValue" options:NSKeyValueObservingOptionNew context:nil];
self.vectorStructValue = (vec3struct){1,2,3};
self.vectorUnionValue = (vec3union){4,5,6};
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"keyPath %# did change, object: %#", keyPath, [object description]);
}
#end
Output:
2013-01-12 17:38:26.447 KVOTest[57522:303] keyPath vectorStructValue did change, object: <AppDelegate: 0x100614200>
Am I doing something wrong or is this a bug or missing feature in the Objective-C runtime/KVO implementation?
Note: I know I can implement this manually, by overriding the property setter, but this is not the point of this question. The answer should give me an idea of why the automatic KVO doesn't work in this case.
Update: Just to make this clear, this is a simple test case comparing the KVO observer on a struct property to that on a union property. These properties are not interrelated. They have independent ivars with independent memory backing stores. You can remove the struct property and run the test, still the same result – no KVO observer event for the union property.
The properties aren't related in OP's question. I misread that in a fever induced hallucination.
Unions are just flat out busted in KVO/KVC. Leaving the text below because it is still interesting.
KVO doesn't work by watching memory or playing any such tricky shenanigans like that. It works by dynamically creating a subclass on the fly, overriding the setter method, and invoking the willChange.../didChange... methods automatically when the setter is called.
Thus, you effectively have 2 properties with 1 backing store. As far as KVO is concerned, though, they are in total isolation from each other.
What you want is dependent keys. You can use +keyPathsForValuesAffectingValueForKey: to create a dependency between the two keys such that calling either setter will trigger a change for the other property.
I don't know if it supports co-dependnence; if it supports what would effectively be a circular dependency.
Alternatively, you ought to be able to override the setter to call willChange/didChange for the other property (as well as the property being changed).
The related keys would be used if you want willChange/didChange to fire for both keys if either property changes. I.e. if you muck with the struct, the union effectively changes and observers of the union property should see a will/did change in response to setting the struct version.
I just tested it. You're right. Something is odd with unions. It is flat out broken. All of the above still remains true, but it does no good.
Radar filed: rdar://problem/13003794
Oooh... neat. KVO w/unions simply doesn't work. It appears that the runtime simply does not even recognize that the class has a key called vectorUnionValue at all.
I added:
+ (NSSet *)keyPathsForValuesAffectingVectorStructValue
{
return [NSSet setWithObject:#"vectorUnionValue"];
}
+ (NSSet *)keyPathsForValuesAffectingVectorUnionValue
{
return [NSSet setWithObject:#"vectorStructValue"];
}
Which caused a runtime exception:
2013-01-12 12:05:11.877 djkdfjkdfjkdf[51598:303] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AppDelegate 0x10010a520> valueForUndefinedKey:]: this class is not key value coding-compliant for the key vectorUnionValue.'
The short version of the question:
I have a class with a ton of declared properties, and I want to keep track of whether or not there have been any changes to it so that when I call a save method on it, it doesn't write to the database when it isn't needed. How do I update an isDirty property without writing custom setters for all of the declared properties?
The longer version of the question:
Let's say that I have a class like this:
#interface MyObject : NSObject
{
#property (nonatomic, retain) NSString *myString;
#property (nonatomic, assign) BOOL myBool;
// ... LOTS more properties
#property (nonatomic, assign) BOOL isDirty;
}
...
#implementation MyObject
{
#synthesize myString;
#synthesize myBool;
// ... LOTS more synthesizes :)
#synthesize isDirty;
}
Attempt 1
My first thought was to implement setValue:forKey: like this:
- (void)setValue:(id)value forKey:(NSString *)key {
if (![key isEqualToString:#"isDirty"]) {
if ([self valueForKey:key] != value) {
if (![[self valueForKey:key] isEqual:value]) {
self.isDirty = YES;
}
}
}
[super setValue:value forKey:key];
}
This works perfectly until you set the value directly with a setter (i.e. myObject.myString = #"new string";), in which case setValue:forKey: isn't called (I'm not sure why I thought that it would be, lol).
Attempt 2
Observe all properties of self.
- (id)init
{
// Normal init stuff
// Start observing all properties of self
}
- (void)dealloc
{
// Stop observing all properties of self
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// set isDirty to true
}
I'm pretty sure that this will work, but I think that there must be a better way. :)
I also want this to be automatic, so that I don't have to maintain a list of properties to watch. I can easily see forgetting to add a property to the list when maintaining this down the road and then trying to figure out why my object sometimes doesn't get saved.
Hopefully I'm overlooking a much simpler approach to this problem!
Final Solution
See my answer below for my final solution to this. It is based on the answer provided by Josh Caswell, but is a working example.
A little introspection should help out here. The runtime functions can give you a list of all the object's properties. You can then use those to tell KVO that dirty is dependent on that list. This avoids the maintainability problem of having to update the list of properties by hand. The one caveat is that, like any other solution involving KVO, you won't be notified if the ivar is changed directly -- all access must be through setter methods.
Register to observe self's dirty key path in init, and add this method, creating and returning an NSSet with the names of all the class's properties (except #"dirty", of course).
#import <objc/runtime.h>
+ (NSSet *)keyPathsForValuesAffectingDirty
{
unsigned int num_props;
objc_property_t * prop_list;
prop_list = class_copyPropertyList(self, &num_props);
NSMutableSet * propSet = [NSMutableSet set];
for( unsigned int i = 0; i < num_props; i++ ){
NSString * propName = [NSString stringWithFormat:#"%s", property_getName(prop_list[i])];
if( [propName isEqualToString:#"dirty"] ){
continue;
}
[propSet addObject:propName];
}
free(prop_list);
return propSet;
}
Now an observation of dirty will be triggered whenever any of this class's properties are set. (Note that properties defined in superclasses are not included in that list.)
You could instead use that list to register as an observer for all the names individually.
It may be a bit overkill depending on your needs, but CoreData provides everything's needed to manage object states and changes. You can use a memory based data store if you do not want to deal with files, but the most powerful setup uses SQLite.
So then, your objects (based on NSManagedObject) will inherit a handful of useful methods, like -changedValues which lists the changed attributes since the last commit or -committedValuesForKeys: nil which returns the last committed attributes.
Overkill possibly, but you do not have to reinvent the wheel, you do not need to use a third party library, and it will need only a few lines of code to make it work nicely. Memory usage will be impacted quite a fair bit, but not necessarily for the bad if you choose to use a SQLite datastore.
Core Data apart, using KVO is the way to go to implement your own snapshot mechanism or change manager.
My final solution (Thanks Josh Caswell for the example!):
- (id)init
{
if (self = [super init])
{
[self addObserver:self forKeyPath:#"isDirty" options:0 context:NULL];
}
return self;
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:#"isDirty"];
}
- (BOOL)loadData
{
// Load the data, then if successful:
isDirty = NO;
return YES;
}
- (BOOL)saveData
{
if (!self.isDirty)
{
return YES;
}
// Save the data, then if successful:
isDirty = NO;
return YES;
}
// isDirty is dependant on ALL of our declared property.
+ (NSSet *)keyPathsForValuesAffectingIsDirty
{
unsigned int num_props;
objc_property_t *prop_list = class_copyPropertyList(self, &num_props);
NSMutableSet * propSet = [NSMutableSet set];
for( unsigned int i = 0; i < num_props; i++ )
{
NSString * propName = [NSString stringWithFormat:#"%s", property_getName(prop_list[i])];
if(![propName isEqualToString:#"isDirty"] )
{
[propSet addObject:propName];
}
}
free(prop_list);
return propSet;
}
// If any of our declared properties are changed, this will be called so set isDirty to true.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"isDirty"])
{
isDirty = YES;
}
}
I don't know what all of your properties are, but you could try "superclassing" them. Create an object ObservedObjectand then make custom classes for all of your objects that are subclasses of this object. Then either put an isDirty property on ObservedObject and look at it, or send a notification to your program when it is changed. This might be a lot of work if you have many different types of objects, but if you have mostly many of the same object it shouldn't be too bad.
I'm interested to see if this is a viable solution or if a good solution can be found for this kind of problem.
One option would be in your save method to get the old version myObject and do something like
if (![myOldObject isEqual:myNewObject]) {
//perform save
}