I want to track changes of NSManagedObject properties, in order to keep NSData *lastUpdate property "up to date"
There are several approaches to get Notified when NSManagedObject changes its properties
I. First is to override the setter Methods of all properties you want to track. Which is quite complicated in NSManaged object - check it here
II. Second could be a good one. You can just override "didChangeValueForKey" method That is called on every property change.
-(void)didChangeValueForKey:(NSString *)key{
[super didChangeValueForKey:key];
NSLog(#"Value for key:%# has changed", key);
}
Unfortunately we should not override this method due to the documentation that says...:
"You must not override this method."
III. Key-value-observing leads us back to IInd approach, with overriding "didChangeValueForKey".
upd.
IV. I tried to override -willSave method
-(void)willSave{
NSArray *observedKeys = #[#"name", #"imageType"];
NSDictionary * changesALL = self.changedValues;
for (id key in changesALL){
if ([observedKeys containsObject:key]){
self.lastUpdate = [NSDate date];
NSLog(#"updated For key: %#", key);
}
}
}
This led infinitive loop, which is described in documentation.
(altho the right approach is described here, so I've answered this question already)
If you want to update a persistent property value, you should typically test for equality >of any new value with the existing value before making a change. If you change property >values using standard accessor methods, Core Data will observe the resultant change >notification and so invoke willSave again before saving the object’s managed object >context. If you continue to modify a value in willSave, willSave will continue to be called >until your program crashes.
For example, if you set a last-modified timestamp, you should check whether either you >previously set it in the same save operation, or that the existing timestamp is not less >than a small delta from the current time. Typically it’s better to calculate the timestamp >once for all the objects being saved (for example, in response to an >NSManagedObjectContextWillSaveNotification).
A suitable solution for your use case to override the willSave method and use it to set the new lastUpdated value. This method is called automatically on dirty objects before they are saved into the context.
If you need to verify what is dirty you can use the contents of the changedValues property.
So after all I figured out that the best solution to track changes of Managed Object is to register for NSManagedObjectContextWillSaveNotification instead, and set the timestamp on all updated and inserted objects in the managed object context. The registered method could look like this:
-(void)contextWillSave:(NSNotification *)notify
{
NSManagedObjectContext *context = [notify object];
NSDate *dateOfTheLastModification = [NSDate date];
for (NSManagedObject *obj in [context insertedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastUpdate"];
}
for (NSManagedObject *obj in [context updatedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastUpdate"];
}
}
This assumes that all your entities have a lastModifiedDate attribute, otherwise you have to check the class of the objects.
To avoid the infinite loop, try this magic:
- (void)willSave{
if(![self.changedValues objectForKey:#"localModificationDate"]){
self.localModificationDate = [NSDate date];
}
else{
[super willSave];
}
}
Once the modification date has been set it won't set it again for the current save. There is a side affect that if the save fails and you save successfully again, I reckon the date will be the from the previous save attempt.
This is fine if you are saving the context after every edit, but the usual design of core data is to only save either at app suspend or after a long time. So it's likely the lastUpdate will be needed for something before then and it won't have the new value yet.
Related
Being a ReactiveCocoa newbie, I'm hoping for some advice with this:
I'm trying to create a dynamic form that contains multiple Field objects parsed from an XML file. Each Field can have muliple validation rules that will run against the Field's NSString *value param.
For the RAC part of the question-
inside each Field object, I want to bind BOOL completed to a signal that checks the Field's *value param against an array of rules. So far I've gotten here with my thinking:
#implementation Field
self = [super init];
if (self) {
RAC(self, completed) = [RACObserve(self, value) filter:^BOOL(NSString *fieldValue) {
NSLog(#"%s::self.completed = %d\n", sel_getName(_cmd), self.completed); // trying to watch the values here, with no luck
NSLog(#"%s::fieldValue = %#\n", sel_getName(_cmd), fieldValue); // same here, I'd like to be able to view the `*value` here but so far no luck
return [self validateCurrentValue]; // currently this method just checks value.length > 5
}];
}
return self;
The *value param has already been bound to my view model (successfully) and it gets updated each time a textfield changes.
What I'm looking for is a basic example or best-practice, the code above crashes when run so I know I'm missing something fundamental.
Thanks all
-filter: is simply passing values from RACObserve(self, value) through unchanged, but only if the block returns YES. So that means you're trying to set completed to values of whatever type value is. That's Probably Bad®.
But the good news is that you're really close!
Instead of filtering, you want to transform. You want to take every value and map it to something other thing. Namely whether that value passes validation. To do that, we use -map::
RAC(self, completed) = [RACObserve(self, value) map:^(NSString *fieldValue) {
return #([self validateCurrentValue]);
}];
I have a project at https://github.com/niklassaers/NJSNotificationCenter with so far only two unit tests. One of them runs, one of them runs 60% of the time. The remaining 40% of the time, it will fail because my NSMutableValue contains a nil value, even though I have never put in a nil value (nor should that be possible)
The problem arises here:
- (void) addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject priority:(NSInteger)priority {
NJSNotificationKey *key = [[NJSNotificationKey alloc] initWithObserver:observer name:aName object:anObject];
NSLog(#"Key is: %p", key);
key.priority = priority;
NJSNotificationValue *value = [[NJSNotificationValue alloc] initWithSelector:aSelector];
NSAssert(value, #"Value cannot be nil!");
#synchronized(observers) {
observers[key] = value;
NSLog(#"Key: %p\tValue: %p\t%#", key, value, observers);
if(observers[key] == nil)
NSLog(#"This can't be!");
}
}
I make a key, it is not nil, I make a value, it is not nil, I add it to my dictionary and get it back from the dictionary, but now it is nil! This makes no sense to me.
I have wrapped every access to observers (a local instance variable) in a #synchronized block just in case there was any other threading going on (there isn't).
Please check out my code (BSD license) and have a look at it, and help me understand how this can be. If you'd like, I'd love to pair program on this with you, I'm #niklassaers on Twitter
You haven't implemented hash.
https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/Collections/Articles/Dictionaries.html#//apple_ref/doc/uid/20000134-SW8
Keys must implement the hash and isEqual: methods because a dictionary
uses a hash table to organize its storage and to quickly access contained
objects
The dictionary is copying your key object and storing that - when it tried to lookup the original key object, it does not find it because the hash values do not match.
I have the following method which is called within a FOR Loop and is called several times, each time iterating through an NSDictionary object to create and set a note object :
- (BOOL)updateById:(NSString *)entityId
withData:(NSDictionary *)dataDictionary {
DLog(#"Updating %#", [_entityClass description]);
if (_entityIdentifier == nil) {
DLog(#"entityIdentifier has not been set");
}
NSManagedObjectContext *context = ContextForThread;
id note = [_entityClass findFirstByAttribute:_entityIdentifier
withValue:entityId
inContext:context]; //This is running slowly ?
[note setValuesFromDictionary:dataDictionary];
BOOL changes = YES;
if ([note changedValues].count == 0) {
changes = NO;
DLog(#"Has NOT changed - Dont save");
}
else {
DLog(#"Has changed");
}
return changes;
}
I am trying to optimise this code and have noticed that the findFirstByAttribute method seems to be rather slow. Is there anyway I can optimise this method ?
Fundamentally, the problem is that you're doing a lot of fetches, and lots of fetches mean lots of work. Your goal here should be to reduce the number of fetches, most likely by doing them all in one shot and then refactoring your code to use the results. For example, if the entityId values are known in advance:
Fetch all instances using the known entityId values. I don't know if MR has a shortcut for this. Using Core Data directly, you'd something like the following with the fetch. The results of the fetch would be all instances where the value of _entityIdentifier is in the entityIds array:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K in %#", _entityIdentifier, entityIds);
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey: _entityIdentifier ascending:YES];
Refactor your method above so that you pass in both a managed object and the dictionary of values you want to assign to that object.
There are other ways to approach this, but one way or another you should fetch multiple objects at once instead of doing a separate fetch for each one.
Setting the attribute to be indexed should help a bit.
Other than that consider doing batch updates if you call this method very often.
You could use MR_findAllWithPredicate to make a single DB query and the update values for each retrieved object.
I have a situation where troops can attack buildings. Each troop keeps a pointer to its target.
#property (nonatomic, weak) Building *target;
In an update loop, the troops periodically cause damage to their target.
if (_target)
{
if (/*enough time has passed since last attack, attack again*/)
{
[_target attack];
if (_target.health <= 0)
{
[_target removeFromParentAndCleanup:YES]; //Cocos2d
_target = nil;
}
}
}
else /* Find new target */
The problem is:
troop1 deals the blow that fells building1 and moves on to building2
troop2 was attacking building1 but waits until its next attack to determine that building1 is now nil.
I realise the problem is that troop2's pointer has not been set to nil and instead I should be checking that the value of the pointer is nil.
I tried using if (*_target) but was met with the message
Statement requires expression of scalar type
If there a way to achieve this kind of comparison in Objective-C? What other options are there for determining when a value has changed? KVO? Some extensive delegate pattern?
It is the pointer itself that is set to nil when the object it points to is deallocated. if (objectPointer == nil) is always the way to check if an object is nil in Objective-C/Cocoa. If the pointer is not nil, it means the object in question has not in fact been deallocated. If you dereference a pointer to an object, you get a struct, hence the compiler error about needing a scalar value in the if expression.
So, in your case, if if(self.target != nil) is not giving you the result you expect, you should look for remaining strong references to the target (from other objects).
More broadly, as hinted at by trojanfoe's answer, you're relying on ARC's zeroing weak reference behavior for real program logic. In theory this is OK, as (contrary to his initial statement), ARC's zeroing weak behavior is reliable/deterministic. But, it does mean that you have to ensure that targets are always deallocated when they're no longer on the playing field (or whatever). This is a bit fragile. Zeroing weak references are intended as a way to avoid retain cycles (essentially a form of memory leak), rather than as a way to implement logic the way you're doing. The gist of trojanfoe's solution, where you explicitly register and unregister targets as necessary, is probably a more robust solution.
There may be something that I have overlooked here, but to check if the target2 property is nil, just do:
if ( self.target2 == nil ) {
// Something
}
I think you are relying too heavily on the implementation of ARC in that you only know if an object has been removed if the pointer is nil. This is non-portable and can you make any guarantee between the object being released and the pointer becoming nil?
Instead, use a central dictionary of objects, mapped against their unique ID and store just this unique ID rather than the object pointer itself. In this example I'm using a NSNumber for the key using an incrementing integer, but there are probably better keys that can be used. Also Object is the base class of any object you want to store in this dictionary:
// Probably ivars in a singleton class
unsigned _uniqueId = 1;
NSMutableDictionary *_objects;
- (NSNumber *)addObject:(Object *)object
{
NSNumber *key = [NSNumber numberWithUnsignedInt:_uniqueId++];
[_objects setObject:object forKey:key];
return key;
}
- (void)removeObjectForKey:(NSNumber *)key
{
[_objects removeObjectForKey:key];
}
- (Object *)getObjectForKey:(NSNumber *)key
{
return [_objects objectForKey:key];
}
And in your target, simply store the building key:
#property (strong) NSNumber *buildingKey;
and get the building via the methods provided:
Building *building = (Building *)[objectDictionary objectForKey:buildingKey];
if (building != nil)
{
// building exists
}
else
{
// building does not exist; throw away the key
buildingKey = nil;
}
Since target is a weak reference, your code should work "as-is", assuming that [_target removeFromParentAndCleanup:YES]; removes all strong references to the target.
When the last strong reference is removed, all of the weak properties pointing to it will automatically be set to nil. If they are not automatically set to nil, then there is still a strong reference to the target somewhere.
Find and remove that reference, and this will work fine.
I am not using core data. Just to keep it simple, let's say I have data which were formerly of type NSString, and now they are supposed to be objects of a custom class Person whose only ivar is a "name" ivar of type NSString. In the updated version of the app, I want my Person objects to have their "name" set to whatever the NSString was in the saved data. But suppose my people appear in lots of different places in the app, so telling it how to handle each one individually would be a pain.
What is the best way to handle this? In particular, is there some trick I can do to catch it in the un-archiving process? Or do I have to go through every un-archived object and turn the appropriate NSStrings into Person objects?
You can create a utility class that check the value that come back from your unarchiver, run it through this method. If the value is an NSString, then you can construct a new Person object, if not, then just returns that object back.
+ (Person *)personFromStringOrPersonObject:(id)object {
if ([object isKindOfClass:[NSString class]]) {
// Construct your Person object
Person person = [Person new];
person.name = object;
return person;
} else {
return object;
}
}