Using one setter for all model iVars - objective-c

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.

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.

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.

How do you determine if an object (self) with a lot of properties has been changed?

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
}

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.

Responding to setters

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.