KVO With NSMutableArray - objective-c

I have a NSMutableArray property in my AppDelegate called blocks.
I would like to observe whenever an object is added to this array.
I've read other posts, but I can't understand why this isn't working.
In my app delegate class, I implement
- (void)insertObject:(id)obj inBlocksAtIndex:(NSInteger)index
{
[blocks insertObject:obj atIndex:index];
}
In my view controller's init method, I add an observer to my AppDelegate reference.
boardModel = [[UIApplication sharedApplication] delegate];
[boardModel addObserver:self forKeyPath:#"blocks" options:0 context:NULL];
In my view controller's viewDidLoad method, I try invoking the KVO Indexed array accessor I implemented previously,
[boardModel insertObject:[[Block alloc] init] inBlocksAtIndex:0];
Then I implement my observeValueForKeyPath method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"blocks"])
{
NSLog(#"ADDED");
}
}
I've tried adding an NSLog statement before the if statement in observeValueForKeyPath, and it seems as if it's never being called.
I've also tried NSLogging [[boardModel blocks] count], and it says the count is 1 (the object is being added).
I must be missing something.

The catch is that NSArrays don't respect KVO, so observing the key path count won't work.
If this is MacOSX, use NSArrayController. otherwise implement a wrapper class for the array that triggers the KVO calls when adding/removing contents of the array, and passes across all other calls.

You're observing the blocks property of the app delegate, not the blocks array itself. Hopefully the following example will make the difference clear:
// This will fire KVO as you're changing the app delegate's `blocks` property.
appDelegate.blocks = [NSMutableArray array];
// This will not fire KVO as the app delegate's `blocks` property still points
// to the same object; from the app delegate's perspective, nothing's happened.
[appDelegate.blocks addObject:#"Object"];
If you want to be notified when the contents of the blocks array changes, observe a property on the array itself—something like count. Updating your code:
[boardModel.blocks addObserver:self forKeyPath:#"count" options:0 context:NULL];

Did you try
- (void)insertObject:(id)obj inBlocksAtIndex:(NSInteger)index
{
[[self mutableArrayValueForKey:#"blocks"] insertObject:obj atIndex:index];
}

I just open-sourced a very small Objective-C library that adds a delegate to NSMutableArray. It might help you achieve what you were trying to do. Check out FCMutableArray on GitHub

It's not that NSMutableArray is not KVO-compliant in some respects, it's that all KVO-compliant properties must be NSObjects. "count" is an NSUInteger; you can't observe that property directly. If it was an NSNumber object, you could.
From the NSArray header file:
#interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
#property (readonly) NSUInteger count;
Why was the answer accepted, by the way? It's clear the answerer did not test his answer.

Related

Dirty flags on Realm objects

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.

Key-Value Observer not changing on its own property

I am trying to get notifications for when a property called "currentTopViewPosition" changes. I used the following code to register for the changes and receive them:
[self addObserver:self
forKeyPath:#"currentTopViewPosition"
options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionPrior
context:NULL];
Then the receiving side:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Key Path: %#\n change: %#",keyPath, change);
}
But nothing was getting logged for it.
I tested to make sure the value was actually changing by using an NSTimer to print out its value every 5ms and it was changing.
I've never seemed to get Key-value observing to work, so am I doing something wrong? missing a step?
Thanks!
The easiest way to make your property is to redeclare the property as readwrite inside your implementation file.
#property (nonatomic, readwrite, assign) ECSlidingViewControllerTopViewPosition currentTopViewPosition;
Then when setting the value, make sure you use the property setter. E.g. self.currentTopViewPosition = 1
If you are manually setting the value using an ivar directly, you will have to generate the KVO calls manually. Like this:
[self willChangeValueForKey:#"currentTopViewPosition"];
_currentTopViewPosition = 1;
[self didChangeValueForKey:#"currentTopViewPosition"];

How to observe (KVO) count change in an NSMutableArray [duplicate]

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.

Objective-C KVO doesn't work with C unions

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.'

What is best practice to interaction with object in objective-c?

My questions is next:
For example I have object A (this is data model object). Assume that object A have some property (for example request property). Also I have object B (this is my view object).
So my problem is next: when my data model will be changed (the value for request property changed) I want to know about this events in my view (object B)
How to create this interaction between object.
For example in request is written to "some_value" and after this object B immediately know about it.
Thanks for response!
You can use delegation pattern, NSNotifications, callback blocks and even KVO. Choice depends on situation, in your case delegate or callback block would work.
I would use Key Value Observing. Your view controller (not the view itself) would set itself up as an observer for the data model object and when it gets observer notifications, it would update the view.
[myDataObject addObserver: myViewController
forKeyPath: #"request"
options: NSKeyValueObservingOptionNew
context: nil];
// in the view controller you need
-(void) observeValueForKeyPath: (NSString*) path
ofObject: (id) aDataObject
change: (NSDictionary*) changeDictionary
context: (void*) context]
{
if (aDataObject == myDataObject
&& [path isEqualToString: #"request"])
{
// change you are interested in
}
// Call suoer implementation of this method if it implements it
}
Don't forget to remove the observer when you are done with it.
Also, be careful in a threaded environment. Observations are notified on the same thread that the change happens on. If this is not the main thread, you'll need to use -performSelectorOnMainThread:withObject:waitUntilDone: to make any changes to the UI.
If you just want object B to know whats up I would suggest using delegation.
If maybe later you want object C, D and E to know too what happend in object A i would suggest using NSNotification.
For example I have class DataModel. In this step I add observer for my property str. For object I will send my view controller.
.h
#import <Foundation/Foundation.h>
#interface DataModel : NSObject
#property (strong, nonatomic) NSString *str;
- (void)setUpObserver:(id)object;
#end
.m
#import "DataModel.h"
#implementation DataModel
#synthesize str;
- (void)setUpObserver:(id)object
{
[self addObserver:object forKeyPath: #"str" options: NSKeyValueObservingOptionNew context: nil];
}
#end
In my view controller
#import "DataModel.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
dm = [[DataModel alloc] init];
[dm setUpObserver:self];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (object == dm && [keyPath isEqualToString: #"str"])
{
NSLog(#"it's work");
}
}
- (IBAction)changeValue:(id)sender {
dm.str = #"test change value";
}
#end
This is my realization of KVO. Thanks JeremyP for explanation.