Raise an event whenever a property value gets changed - objective-c

I have a class having 2 properties:
#property (copy) NSString *name; //name of the file
#property (copy) NSString *path; //path of the file
#property NSImage *img; //icon of the file
Problem
I want to name and img auto fill whenever path is changed like an event should be raised whenever the path is changed so that I can refresh name and img.
Many thanks in advance. Please note that I am 2 months new to objective-c and I was in the .net world from the last 2 years.

You can go with a well established KVO (Key-Value Observing) technique:
Step 1 : Assuming you already have a model class say MyData with these properties, set your controller class or where you are creating that model object as observer for path something like this:
MyData *data = [[MyData alloc] init];
[data addObserver:self forKeyPath:#"path" options:NSKeyValueObservingOptionNew context:NULL];
Step 2 : Implement observeValueForKeyPath:ofObject:change:context: method to listen to any change in the path property. Something like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
MyData *myData = (MyData *)object;
myData.name = #"My Name";
myData.image = // Set your image here
}
Step 3 : Importantly, do not forget to remove yourself as observer of this property whenever your object is being deallocated. Otherwise you may end up crashing!

(Updated to prevent looping)
You could use a setter function. Instead of using MyClass.name = ... declare a function in that class and use that to change the value:
- (void) setPath:(NSString*)newvalue{
_path = newvalue;
[self updateNameAndPicture];
}
Then, when you change it, use
[InstanceOfMyClass setPath:#"/new/path/"];

Related

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"];

KVO Not Triggering When Value Changes

I'm trying to do some tests with Apple's KVC but for some reason I can't get KVO to trigger when I change a value via KVC.
I have the following code:
#import <Foundation/Foundation.h>
#interface Character : NSObject
{
NSString *characterName;
NSInteger ownedClowCards;
}
#property (nonatomic, retain) NSString *characterName;
#property (nonatomic, assign) NSInteger ownedClowCards;
-(void)hasLostClowCard;
-(void)hasGainedClowCard;
#end
#implementation Character
#synthesize characterName;
#synthesize ownedClowCards;
-(void)hasLostClowCard
{
}
-(void)hasGainedClowCard
{
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"Change");
}
#end
int main()
{
Character *sakura;
Character *shaoran;
//---------------------------------------------------------------------
// Here begins the KVO section.
[sakura addObserver:sakura forKeyPath:#"ownedClowCards" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
//Create and give the properties some values with KVC...
sakura = [[Character alloc] init];
[sakura setValue:#"Sakura Kinomoto" forKey:#"characterName"];
[sakura setValue:[NSNumber numberWithInt:20] forKey:#"ownedClowCards"];
shaoran = [[Character alloc] init];
[shaoran setValue:#"Li Shaoran" forKey:#"characterName"];
[shaoran setValue:[NSNumber numberWithInt:21] forKey:#"ownedClowCards"];
//Done! Now we are going to fetch the values using KVC.
NSString *mainCharacter = [sakura valueForKey:#"characterName"];
NSNumber *mainCharCards = [sakura valueForKey:#"ownedClowCards"];
NSString *rival = [shaoran valueForKey:#"characterName"];
NSNumber *rivalCards = [shaoran valueForKey:#"ownedClowCards"];
NSLog(#"%# has %d Clow Cards", mainCharacter, [mainCharCards intValue]);
NSLog(#"%# has %d Clow Cards", rival, [rivalCards intValue]);
[sakura setValue:[NSNumber numberWithInt:22] forKey:#"ownedClowCards"];
}
Like you can see it's really, really basic code, so I'm ashamed I can't get this to work for whatever reason. Everything I'm trying to do is to get a notification when ownedClowCards changes. I am registering the observers. When I run my program, I expect to see the message "Changed" once when the program is done running. But it never does. Changed is never printed to my program so I assume observeValueForKeyPath:ofObject:change:context: is not getting called.
Any help?
[sakura addObserver:sakura forKeyPath:#"ownedClowCards" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
You are doing the above before initializing sakura. Of course setting nil to observe changes to nil does nothing.
You add your observer to an object that doesn't exist yet.
Character *sakura;
This simply declares the variable, but doesn't actually allocate or initialize it yet.
Try calling sakura = [[Character alloc] init]; before you register as an observer.
And by the way NSString properties usually use the copy flag and not retain. And a memory management flag (assign) doesn't make sense in the case of a primitive type (NSInteger).

If I write a custom property getter method, will KVO still operate if the getter returns a value by accessing a value from another object?

Assume that I have a class with a readonly property on it.
//MyClass.h
#interface MyClass
#property (readonly) NSInteger MonitorMe;
#end
Now, let's assume the point of this property is to monitor changes of another property, within another object, and when the property is "observed" it returns a derived value by inspecting a value from the other, external object.
//MyClass.m
#implementation
#synthesize MonitorMe;
-(NSInteger) getMonitorMe
{
return globalStaticClass.OtherNSInteger;
}
... Inits and Methods ...
#end
Now, let's assume that some where I create an instance of the MyClass object, and I want to add a KVO observer on the MonitorMe property.
//AnotherClass.m
#implementation AnotherClass.m
#synthesize instanceOfMyClass;
-(id)init
{
...
instanceOfMyMethod = [MyClass init];
[MyClass addObserver: self
forKeyPath: #"MonitorMe"
options: NSKeyValuObservingOptionNew
context: nil];
...
}
My question is, since the MonitorMe property only monitors the changes of values in an external object, will the observer method execute when the value of globalStaticClass.OtherNSInteger changes? Also, if the answer is yes, how is this done?
If this works, it would seem like compiler voodoo to me.
Note
I don't think it makes a difference, but I am using ARC for this implementation and I'm compiling for an iOS device. I doubt there are compilation differences between OS X and iOS for this type of question but, if it matters, I have an iOS project that requires such an implementation outlined above.
Also, the example outlined above is a very basic setup of my actual needs. It could be argued that I could/should add an observation to the globalStaticClass.OtherNSInteger value instead of the readonly property, MonitorMe. In my actual circumstance that answer is not sufficient because my readonly property is much more complex than my example.
will the observer method execute when the value of globalStaticClass.OtherNSInteger changes?
No, but you can make that happen, via +keyPathsForValuesAffectingMonitorMe (or the more generic +keyPathsForValuesAffectingValueForKey:, if the "globalStaticClass" is actually a property of MyClass. See "Registering Dependent Keys" in the KVO Guide.
Here's a quick mockup:
#import <Foundation/Foundation.h>
#interface Monitored : NSObject
#property NSInteger otherInteger;
#end
#implementation Monitored
#synthesize otherInteger;
#end
#interface Container : NSObject
#property (readonly) NSInteger monitorMe;
#property (strong) Monitored * theMonitored;
- (void)changeMonitoredInteger;
#end
#implementation Container
#synthesize theMonitored;
+ (NSSet *)keyPathsForValuesAffectingMonitorMe {
return [NSSet setWithObject:#"theMonitored.otherInteger"];
}
- (id) init {
self = [super init];
if( !self ) return nil;
theMonitored = [[Monitored alloc] init];
[theMonitored setOtherInteger:25];
return self;
}
- (NSInteger)monitorMe
{
return [[self theMonitored] otherInteger];
}
- (void)changeMonitoredInteger {
[[self theMonitored] setOtherInteger:arc4random()];
}
#end
#interface Observer : NSObject
#end
#implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Observing change in: %# %#", keyPath, object);
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
Observer * o = [[Observer alloc] init];
Container * c = [[Container alloc] init];
[c addObserver:o
forKeyPath:#"monitorMe"
options:NSKeyValueObservingOptionNew
context:NULL];
[c changeMonitoredInteger];
[c changeMonitoredInteger];
}
return 0;
}
P.S. Cocoa style notes: properties/variables should have lowercase initial letters, and (this is actually more important now because of ARC) don't name accessor methods to start with "get" -- that has a specific meaning in Cocoa involving passing in a buffer and getting data back by reference.

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.

Using one setter for all model iVars

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.