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.'
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.
I have a KVO-able class (call it Observee), which affectedValue dynamic property is affected by affectingValue property. The dependency between the properties is defined by implementing +keyPathsForValuesAffectingAffectedValue method.
Setting a value to affectingValue notifies that affectedValue has changed as I expected, unless Ovservee is a subclass of NSObjectController. Full example follows:
#interface Observee : NSObject // or NSObjectController
#property (readonly, strong, nonatomic) id affectedValue;
#property (strong, nonatomic) id affectingValue;
#property (strong, nonatomic) NSArrayController *arrayController;
#end
#implementation Observee
#dynamic affectedValue;
- (id)affectedValue { return nil; }
+ (NSSet *)keyPathsForValuesAffectingAffectedValue {
NSLog(#"keyPathsForValuesAffectingAffectedValue called");
return [NSSet setWithObject:#"affectingValue"];
}
#end
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (strong, nonatomic) Observee *observee;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
self.observee = [[Observee alloc] init];
[self.observee addObserver:self
forKeyPath:#"affectedValue"
options:NSKeyValueObservingOptionNew
context:NULL];
NSLog(#"setting value to affectingValue");
self.observee.affectingValue = #42;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(#"affected key path = %#", keyPath);
}
#end
The example works fine and outputs as the following when Observee derives NSObject:
keyPathsForValuesAffectingAffectedValue called
setting value to affectingValue
affected key path = affectedValue
but when Observee derives NSObjectController:
keyPathsForValuesAffectingAffectedValue called
setting value to affectingValue
(note that "affected key path = affectedValue" is absent.)
It seems that keyPathsForValuesAffectingAffectedValue is called in both cases but it is no-op in the latter.
Also, any key paths involving an instance of (subclass of) NSObjectController won't affect other key paths, such as:
#implementation SomeObject
// `someValue` won't be affected by `key.path.(snip).arrangedObjects`
+ (NSSet *)keyPathsForValuesAffectingSomeValue {
return [NSSet setWithObject:#"key.path.involving.anNSArrayController.arrangedObjects"];
}
#end
How do I declare dependency between key paths in such cases? And, why is this whole thing happening?
(Yes, I know about will/didChangeValueForKey: and friends, but wrapping up every affecting key path with a(nother) setter is terrible and I'd like to avoid it.)
NSController and its subclasses are full of KVO "black magic" and unexpected behaviors. (For another example, they don't respect certain KVO options like NSKeyValueObservingOptionPrior) You will be disappointed if you expect them to behave like "normal" objects with respect to KVO. They exist primarily to support Cocoa bindings. Although at first glance bindings may look like mere syntactic sugar on top of KVO, you can see (by overriding the KVO supporting methods of a bound-to object and setting breakpoints in them) that there's actually quite a bit more going on underneath the covers than a simple KVO observation.
Please file bugs with Apple to increase the likelihood that they'll fix (or at least document) these sorts of issues/behavior.
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.
I have a series of models for my application. Across all these models there are (will be) some 200 or 300 instance variables. The application stores its persistent data on a web-based server (MySQL - but I guess that part doesn't matter). Whenever a model iVar is updated I need to make a call to the server to update the appropriate value for that iVar.
My current model strategy is (header file):
#interface MyModel : NSObject {
NSString * firstName;
NSString * lastName;
}
#property (readwrite, copy) NSString * firstName;
#property (readwrite, copy) NSString * lastName;
#end
(implementation file):
#implementation MyModel
#synthesize firstName;
#synthesize lastName;
-(id)init {
[super init]
[self setFirstName:#"George"];
[self setLastName:#"Kastanza"];
return self;
}
-(void)setFirstName:(NSString *)aName {
// call method to update server with new value here
firstName = aName;
}
-(void)setLastName:(NSString *)aName {
// call method to update server with new value here
lastName = aName;
}
#end
The problem is that if I have 200 or 300 iVar's all needing to go through the same update call to the server that means writing a lot of setters. Moreover, if I need to make a change to the method call, I'd have to update each and every method in every setter i the entire application.
Is there a process by which I could run every set of an iVar through a method first, before setting?
I thought of having just a NSMutableDictionary per model object to store all of the iVar's, but that abstracts the setters and getters and may introduce a big memory footprint for so many dictionaries. However, doing it this way means that every time the dictionary is set I could pass it through one method.
As I understand it dynamically adding iVar's at runtime to an object model is considered a bad thing because of the pointer referencing for any subclasses that may be dependent upon the model (the subclass pointer doesn't get offset unless a complete recompile is done).
Any ideas and suggestions much appreciated.
Update
Based upon Ole's recommendation here is the solution (although it uses a little more code than a few lines unfortunately)...
In the model I added a method that I can set when I need to. I didn't call the method directly from the init, because adding a whole bunch of results returned from the server would trigger the observers for every object added. So I call the method after I have initialized and updated the first grab from the server.
Here's the code...
-(void)registerObservers {
[self addObserver:self
forKeyPath:#"firstName"
options:NSKeyValueObservingOptionNew
context:NULL];
[self addObserver:self
forKeyPath:#"lastName"
options:NSKeyValueObservingOptionNew
context:NULL];
}
Then I add the observer to the model:
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqual:#"firstName"]) {
// Do whatever I need to do
}
if ([keyPath isEqual:#"lastName"]) {
// Do whatever I need to do
}
}
In my real implementation I also happen to post a notification of the object set to self so that I can update anything that should be listening but isn't paying attention (like stuff in NSArrayControllers).
Use Key-Value Observing. You have to manually register yourself as an observer for every property, though.