AddObserver forkeypath:: not responding - ios

I have the following code which I'm using to try and observe a variable, and regenerate text when it changes. But so far nothing happens:/
-(void)viewdidload{
float Index = 1;//declared in header locally
[self addObserver:self forKeyPath:#"Index" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self generateAdviceText];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(#"inside");
if([keyPath isEqualToString:#"Index"]){
NSLog(#"INDEX CHANGED");
[self generateAdviceText];
}
}
-(void)generateAdviceText{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *name = ((User *)appDelegate.users[self.currentUserIndex]).name;
NSString *clothWear = #"HAT";
NSMutableAttributedString *adviceString = [[NSMutableAttributedString alloc]initWithString:#"\"..."];
[adviceString appendAttributedString:[self boldString:name]];
[adviceString appendAttributedString:[self normalString:#", remember be extra aware of the cats today. The index is "]];
[adviceString appendAttributedString:[self boldString:[#(Index) stringValue]]];
[adviceString appendAttributedString:[self normalString:#", sth "]];
[adviceString appendAttributedString:[self boldString:clothWear]];
[adviceString appendAttributedString:[self normalString:#" sth "]];
[adviceString appendAttributedString:[self boldString:sth]];
[adviceString appendAttributedString:[self normalString:#"...\""]];
self.adviceLabel.attributedText = adviceString;
}
Can anybody spot my mistake? Thanks

Key-Value Observing does not generate change notifications for simple assignments to instance variables (or any other kind of variable).
In most cases, you need to call the setter of the property to generate a change notification for that object and that property.
At a low level, KVO only generates change notifications if the -willChange... and -didChange... methods from the NSKeyValueObserving informal protocol are called. Those methods are called in specific circumstances:
If a properly-named setter or collection mutation accessor for the property named by the key is called (assuming +automaticallyNotifiesObserversForKey: return true for that key). This is the best way.
If one of the -set... methods from the NSKeyValueCoding informal protocol, such as -setValue:forKey:, is called (again, assuming +automaticallyNotifiesObserversForKey: return true for that key).
If a collection proxy returned by one of the -mutable...ValueForKey: methods of the NSKeyValueCoding is sent mutation methods (again, depends on +automaticallyNotifiesObserversForKey:).
If some code (usually in the class defining the property) manually calls the -willChange... and -didChange... methods.
As Joe Shang noted, though, it's questionable for an object to observe itself. If an object wants to know when one of its properties has been changed, it should put the relevant code into its setter. Of course, it then has to modify its property exclusively using that setter, never by setting the instance variable directly (just like for KVO).
In the code you posted, though, it's just as well that you didn't get the KVO change notifications you expected. Your code would infinitely recurse until it crashed when it overflowed the stack. You attempted to make it so that changing the index calls -generateAdviceText and that -generateAdviceText changes the index.

What's the Index type? static variable or property? The keyPath in addObserver:forKeyPath:options:contexts: must be the property of some object (KVC compatible), you can read NSHipster's acticle and objc.io #7 to get more.
In the other hand, if Index is property, you don't need to use KVO here. You can override the setter method of Index and do some action when Index is update.
- (void)setIndex:(int)index
{
_index = index;
// add you generateAdviceText method here
}

Related

KVO - Observe property of an object contained in a NSArray

Still not able to digest KVO with NSArray of objects. My requirement is e.g. suppose in a garage there are multiple cars. And I want to observe for change in car tyre properties like is the front tyre upgraded or the back one.
Car.h
#property(nonatomic, strong) NSString *frontTyre;
#property(nonatomic, strong) NSString *backTyre;
Garage.h
#property(nonatomic, strong) NSArray *cars; //Think there are two cars in the garage.
In CarOwner.m
[Garage addObserver:self forKeyPath:#"cars" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];]
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"cars"])
{
//Here I am able to get the car object which got a type upgrade.
//But how to know which tyre got upgraded?
}
}
While upgrading the car tyre I am doing:
[self willChange:NSKeyValueChangeSetting valuesAtIndexes:index forKey:#"remoteUsers"];
[car1.frontTyre = #"upgraded"];
[self didChange:NSKeyValueChangeSetting valuesAtIndexes:index forKey:#"remoteUsers"];
Should I go this or this way? Or any other suggestion you may have, please.
You can use add observer as follow:
[Garage.cars addObserver:self toObjectsAtIndexes:[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, Garage.cars.count)] forKeyPath:#"frontTyre" options:0 context:nil];
Remember to remove observer when necessary.
After reading more about KVO understood that there is no way that one can observe properties of objects contained in a mutable array. We can observe properties of immutable array of objects through (as #zylenv mentioned)
[object addObserver:observer toObjectsAtIndexes:[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, array.count)] forKeyPath:#"property" options:0 context:nil];
However to observe mutable array of object's properties here are the steps:
Add observer to the array to get notified on object insertion and deletion operation.
When received a insertion notification, add an observer to the newly added object's properties that is of interest
And when a deletion notification is received simply remove the observer from the object that is added in step#2 (NOTE: the removed object will be in the "old" key of the change dictionary)

Using nested key paths in KVO

Suppose i want to observe a property named 'isEnabled' on a property named 'controller' on self. AFAIK i have two options for installing such kind of observation:
1. [self.controller addObserver:self forKeyPath:#"isEnabled" options:0 context:nil];
2. [self addObserver:self forKeyPath:#"controller.isEnabled" options:0 context:nil];
I noticed the practical difference between the two approaches - on the second approach i will get a notification if the 'controller' object on self was replaced while with the first approach I will be notified only when 'isEnabled' property is changed on the same instance on which I installed the observation.
My question is where the hell is this documented if at all? I know it works but should I use it?
I could not find any mention of such behavior in Apple docs, though some other dudes mentioned it in forums. Any reference will be gladly accepted.
Thanks.
It's not just that you'll get a change notification if the controller property changes, it's that KVO will switch to tracking the isEnabled property of the new controller and stop tracking the isEnabled property of the old controller.
This is implicit in the notion of a key path. Key-Value Observing is built on top of Key-Value Coding. The Key-Value Coding Programming Guide says this about key paths in Key-Value Coding Fundamentals:
A key path is a string of dot separated keys that is used to specify a sequence of object properties to traverse. The property of the first key in the sequence is relative to the receiver, and each subsequent key is evaluated relative to the value of the previous property.
For example, the key path address.street would get the value of the address property from the receiving object, and then determine the street property relative to the address object.
The meaning of -addObserver:forKeyPath:options:context: is not "follow the key path to the final element and observe that property on the second-to-last object". It's "observe this key path of the receiver object". The key path is always considered starting from the receiver.
Put another way, for your second code example, it's not "observe the isEnabled property of the controller of self" (that's the meaning of your first example). The meaning is "observe the controller.isEnabled of self. Any time that the result of evaluating the expression [self valueForKeyPath:#"controller.isEnabled"] has or might have changed, notify me."
I know it works but should I use it?
Yes, you should use it. It's the intended meaning of the API. It's why the method describes its parameter as a key path rather than just a key.
on the second approach i will get a notification if the 'controller'
object on self was replaced
I would rephrase by saying that on the second approach you'll get notified if the controller object gets replaced or if its isEnabled property changes. In other word when controller.isEnabled changes (as explained by Ken's answer).
Check this example:
- (void)viewDidLoad {
[super viewDidLoad];
self.controller = [[ViewController2 alloc] init];
[self.controller addObserver:self forKeyPath:#"isEnabled" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self forKeyPath:#"controller.isEnabled" options:NSKeyValueObservingOptionNew context:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.controller.isEnabled = !self.controller.isEnabled;
// replace controller
[self.controller removeObserver:self forKeyPath:#"isEnabled"];
self.controller = [[ViewController2 alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.controller.isEnabled = !self.controller.isEnabled;
});
});
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"KVO %d %# %p", self.controller.isEnabled, keyPath, self.controller);
}
We'll get 4 KVO notifications:
KVO 1 controller.isEnabled 0x7fbbc2e4b4e0 <-- These 2 fire together when we toggle isEnbled
KVO 1 isEnabled 0x7fbbc2e4b4e0 <-- So basically 1. and 2. behave the same
KVO 0 controller.isEnabled 0x7fbbc2e58d30 <---- controller was replaced
KVO 1 controller.isEnabled 0x7fbbc2e58d30 <---- Toggle on the new instance

Possible memory leak with KVO and context

I'm trying to make a little binding system between my UILabel and my object Data using KVO. If my UI change, my data have to change, and if my data change my UI should refresh to display the new value.
The biggest issue I have is that I need to cast a custom object to a void* (context) with __bridge_retained void* - or CFBridgingRetain() - but I don't know where I should call CFBridgingRelease(). If call it in observeValueForKeyPath method I get a bad access error (I guess because my Reference Count to the object pointed by context is 0)
// viewDidLoad
// binding my label text with a custom data object
[self bindObject:_myLabel withPath:#"text" toObject:_user path:#"name"];
-(void) bindObject:(id)uiObj withPath:(NSString *)uiPath toObject:(id)dataObj path:(NSString *)dataPath
{
// custom object storing the object I want to bind and his path
PLSObjectPath* op = [[PLSObjectPath alloc] init];
op.theObj = dataObj;
op.thePath = dataPath;
PLSObjectPath* ob = [[PLSObjectPath alloc] init];
ob.theObj = uiObj;
ob.thePath = uiPath;
/* possible leak because I don't know where to call CFBridgingRelease */
[uiObj addObserver:self forKeyPath:uiPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(op)];
[dataObj addObserver:self forKeyPath:dataPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(ob)];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
PLSObjectPath *obj = (__bridge PLSObjectPath*) context;
PLSObjectPath* pairObj = [[PLSObjectPath alloc] init];
pairObj.theObj = object;
pairObj.thePath = keyPath;
// avoid infinite loop
[obj.theObj removeObserver:self forKeyPath:obj.thePath];
[obj.theObj setValue:change[#"new"] forKeyPath:obj.thePath];
[obj.theObj addObserver:self forKeyPath:obj.thePath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(pairObj)];
}
Traditionally users of this have used a static char * as the context parameter, so as to differentiate the different observeValueForKeyPath messages. That said, it should be possible to do something as you are attempting.
What I would suggest is to switch from a custom object to a Core Foundation one, where you can do you own memory management explicitly. Thus I'd suggest changing PLSObjectPath to CFDictionary. You can first create a NSDictionary, then "transfer" it to the CF domain with the appropriate cast, and pass that CFDictionary object for context (which is now a retained CF object). Recast it in observeValueForKeyPath to a CFDictionary, properly ARC cast it to a NSDictionary, use that, then it should get released if you've done the ARC correctly. This is all a well understood paradyme - moving objects in and out of ARC.
Another way you could do it is us a static NSMutableDictionary, and use the context pointer to go to a int value, which when converted to a NSNumber is the key to the dictionary. If all KVO occurs on the main thread, you don't need to protect the dictionary, but if not then you will need to put all access to the dictionary behind a serial dispatch queue that in forces serial access on one thread.
The memory leak is from [obj.theObj removeObserver:self forKeyPath:obj.thePath]. You are removing the observer but as the system doesn't treat the context as an object, it will not release it. Also, there is no way for you to get the context from the observed object itself.
At some point you will need to stop all observation to allow your view to be deallocated. This should happen from viewDid(Will)Disappear:. To be able to release the PLSObjectPath:s at that point, you will need to store them somewhere, possibly an NSMutableArray. If you will store these anyway for this purpose, you don't need to use __bridge_retained. Also, in this case your PLSObjectPath:s might/should contain a void* pointing to the other context. This way, you save the creation of PLSObject in observeValueForKeyPath:ofObject:change:context:.
Just a comment, you should start the KVO from viewWill(Did)Appear: and not from viewDidLoad. It gives you a better KVO start/stop management and also removes the unnecessary observation while your view is not on the screen.

Change the string of UILabel.text

aClass.aString = #"A";
self.aLabel.text = aClass.aString;
If I change aClass.aString to "B", also want self.aLabel.text change to "B" automatically.
How it works?
Is there a strong link exist that I can built between aClass.aString and self.aLabel.text?
I knew that there some way to do this in objective-c, delegate and KVO, but it get a lot stuff to do, it's there some easy way?
You'll have to manage that yourself. If you look at the definition of the UILabel text property (link) you'll see that it copies the string contents, precisely for this very reason:
#property(nonatomic, copy) NSString *text
You could write a custom setter method for the aString property:
- (void)setAString:(NSString *)aString
{
_aString = aString;
self.aLabel.text = aString;
}
(If you are following the current trend of letting clang generate the backing instance variables and accessor methods for you, then you will probably need to explicitly declare this particular one, i.e. using an instance variable called _aString).
One more way other than overriding the setter method,
- (void)setAString:(NSString *)aString
is using KVO to observe change in the string value.
[self addObserver: self
forKeyPath: #"aString"
options: NSKeyValueObservingOptionNew
context: NULL];
Implement this method. It will get called whenever the string value changes.
-(void) observeValueForKeyPath: (NSString *)keyPath ofObject: (id) object
change: (NSDictionary *) change context: (void *) context
{
//Set the label's text with new value.
}
You may want to use the mechanism better explained here:
Is there any data binding mechanism available for iOS?
The gist of it is there's no nice way to do it; there are, however, messy ways to do it using NSNotificationCenter and listening for specific changes.
UI bindings themselves don't exist on iOS (it was my biggest shock when converting from Mac OS X to iOS).

Inter-Model/NSManagedObject communication

I have two NSManagedObject subclasses: Parent and Child. Parent contains many Child(ren) in an OrderedSet. When state changes in a Child I want the parent to know about it.
If I was programming in another language I might use events, having the Parent listening for events from each of its children, however given that target-action is limited to view components, all Objective C offers me is use of a global NSNotificationCenter. I definitely don't like the idea of a Model tapping into global notifications (listening directly via events is Ok in my book), so it seems my only alternative is Delegation. However using delegation between two NSManagedObjects seems like a dangerous idea, given the difficulty in ensuring one party does not lose its reference to the other.
Does anyone have any suggestions as to how I should be handling this?
Another option is key-value observing. Set it up like this:
const static void *kParentObservingChildSomePropertyContext = &kParentObservingChildSomePropertyContext;
[child addObserver: child.parent forKeyPath: #"someProperty" options: /*see below*/ context: kParentObservingChildSomePropertyContext];
the weird constant definition exists because every observing context should be unique so that you don't tread on superclass or subclass observation contexts. To see what options you need to set, consult the manual and compare with your specific needs. Now whenever that property on your child object changes, your parent will receive:
-(void)observeValueForKeyPath: (NSString *)path ofObject: (id)object change: (NSDictionary *)change context: (void *)context;
Check that you have the correct context for your observation, then handle the change. If you get a different context here, forward the message to super.
When you're done observing the path, you remove the parent as an observer of the child:
[child removeObserver: child.parent forKeyPath: #"someProperty" context: kParentObservingChildSomePropertyContext];
Use the same context pointer you used in -addObserver:forKeyPath:options:context so that the correct instance of the observation is removed.
However using delegation between two NSManagedObjects seems like a dangerous idea, given the difficulty in ensuring one party does not lose its reference to the other.
Delegating, observing and watching for notifications from specific objects all suffer from this problem. You need to make sure that the lifetime of your interest in the notifications matches the lifetimes of the objects involved, otherwise you could easily "leak" observation info or - and this is worse - send notifications to stale object pointers. None of these solutions is immune to that, though in the case of the Delegate pattern you can use a zeroing weak reference to ensure that when the parent object disappears, the child will no longer try to delegate to it.
I pick a field to watch in the view controller that will service the request...
[self addObserver:self forKeyPath:#"clientProgress.dateLastUpdated" options:0 context:nil];
I make sure in that same view controller that I remove the observer on dealloc (or whatever passes for dealloc in ARC).
- (void)dealloc {
[self removeObserver:self forKeyPath:#"clientProgress.dateLastUpdated"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqual:#"clientProgress.dateLastUpdated"]) {
// If this key path has changed, the browser needs to update its display.
NSError *error = nil;
if (![managedObjectContext save:&error]) {
UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:#"Save Error" message:#"Error saving inserted record, contact Tech Support" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[dialog show];
[dialog release];
exit(-1); // Fail
}
if ( changeIsComingFromLibraryInsert ) {
}
[conditioningTableView reloadData];
}
// Essential to call super class implementation - NSArrayController relies heavily on KVO
//[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
Finally, when I want to call the observer, I merely update the date...
// force a save by setting the modified date and the preceeding ViewController with then save and reload from the calling view controller
// as there is an observer running watching this key value
clientProgress.dateLastUpdated = [NSDate date];

Resources