I have created an Entity in CoreData that includes a Transformable attribute type implemented as an NSDictionary. The NSDictionary attribute only contains values of a custom class. The properties of the custom class are all of type NSString. The custom class complies with NSCoding implementing:
-(void)encodeWithCoder:(NSCoder*)coder;
-(id)initWithCoder:(NSCoder *)coder
When saving the Entity for the first time all attributes including the Transformable (NSDictionary) type are properly saved in the DB. When the same Entity is fetched from the DB and updated (including the Transformable attribute) it seems to be updated properly. However, when the app is closed and then reopened fetching the Entity does not show the updated Transformable attribute-type though the rest of the attributes of type NSDate and NSString are up-to-date. The Transformable attribute is the original saved value not the updated value.
Is this a problem with KVO or am I missing something else when trying to save an NSDictionary filled with a custom class to CoreData?
Are you setting the value back into the NSManagedObject? The NSManagedObject will not watch for changes to the transformable object. You need to call the appropriate setter before saving.
I ran into the same problem and ended up switching to NSDictionary as transformable attribute instead of NSMutableDictionary. Just fetch the NSDictionary as mutableCopy, work on that, put the end result into an NSDictionary and reinsert that into the managedObject.
Did the trick for me and i havent found another solution yet.
Related
My NSFetchedResultsController work great, as long as only "basic" attributes get changed. However if I have a label which is calculated and I'm changing some attributes influencing this label in another view controller on the navigation controller stack, this label doesn't get updated.
For example my label should show the amount of a budget position left saved in the entity SpendingCategory.
self.budgetLeftLabel.text = [NSString stringWithFormat:#"%# %#", [[self.spendingCategory getExpendituresAmount] getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(#"left", nil)];
I derive this value from the category on SpendingCategory with this method:
- (NSNumber *)getExpendituresAmount
{
return [self.hasExpenditures valueForKeyPath:#"#sum.amount"];
}
However this label doesn't get any updates by the NSFetchedResultsController. And I have several locations in my app where this doesn't happen because a value is calculated. What do I need to change that these updates happen?
EDIT with datastructure:
Ok my Spending Category datastructure is roughly (for budget):
name (string)
cost (double)
position (integer 16)
Relationsships: hasExpenditures
My Expenditures structure (for tracking):
amount (double)
date (Date)
description (string)
Relationsships: forSpendingCategory
I hope it's clearer now. So why do these values not get updated?
The NSFetchedResultsController gets tickled when attributes in the relevant NSManagedObject instances are updated. If you are changing something that is purely calculated then the update never fires. Why is this relevant?
If you are changing something in the Expenditures entity (btw, entities should be singular in name) and you are watching the Spending Category entity then the NSFetchedResultsController won't fire because you didn't change anything that is relevant.
How to fix this?
Depends. I normally keep that derived value in the entity and persist it. Further, whenever a child changes a relevant value, I have the parent recalculate. This will cause the NSFetchedResultsController to fire.
How do you watch the values?
Either you have the child call a method on the parent (icky) or you have the parent watch the values on its children via KVO (better). Your personal preference decides here.
Update 1
To keep the derived value in the entity you add a new attributed to the entity and store it. Nothing is special about the attribute. It helps to keep in mind that Core Data is not a database. Core Data is your data model that happens to persist to a database if you so choose. Therefore you want to denormalize the database in cases like this.
while I was searching SO to find a good link for watching children, I stumbled across this example.
KVO object properties within to-many relationship
While the accepted answer is not very good, the second answer, using a NSFetchedResultsController is quite interesting and is worth exploring. The basic idea is that your parent objects instantiate a NSFetchedResultsController on -awakeFromFetch or -awakeFromInsert and when it fires, they recalculate the derived value. Thus the value is always up to date and your view controller based NSFetchedResultController instances will fire because the parent object has changed.
I did something similiar time ago, basically you need to store your calculated value in a transient attribute in your CoreData model, rather than implement your own setter and getter. Then in the related NSManagedObject you need to implement two methods:
// this will populate the values when
// the entity is retrieved from the store
-(void)awakeFromFetch {
[self refreshCellInfo];
}
// this will refresh the values when
// the object goes to fault
// (for example when it is off screen)
-(void)willTurnIntoFault {
[self refreshCellInfo];
}
-(void)refreshCellInfo {
// update all your derived values...
}
What method will be called by Core Data on NSManagedObject when setting a value for to-one relationship? What method will be called when adding a value to to-many relationship? Thanks.
/Mikael
Maybe you are looking for something like:
Managed Object Accessor Methods - Core Data Programming Guide
Its like this
managedObjectInstance.property = value;
[self.managedObjectContext save:&error];
Ex: if you want to save username in user entity it will be
_userEntity.username = #"Mikeal Hakman";
[self.managedObjectContext save:&error];
One to Many or Many to May always carries NSSet example
_residenceEntity.Seller = [NSSet setWithObjects:seller, nil];
[self.managedObjectContext save:&error];
Obviously I didn't manage to formulate my question clearly enough. I'll try again.
In a subclass of NSManagedObject I need to know when to-many and to-one relationships are being changed. That includes the very first change when the object is being fetched or inserted. I try all the accessors described in https://developer.apple.com/library/mac/documentation/cocoa/conceptual/coredata/articles/cdAccessorMethods.html to no avail. I can see in my UI that the relationship is there but no accessor methods has been called on my object. Also when I remove the relationship, it goes away in UI but no methods on my object are called. Thanks.
/Mikael
I'm using Simperium and mogenerator in my project. I've added SPManagedObject entity to the model and set that as the parent class for other entities. For each entity, mogenerator automatically creates a _<entityname>.h and _<entityname>.m file that has several convenience accessors. I've modified the mogenerator build script so each of these subclasses SPManagedObject instead of NSManagedObject. The entity classes simply import the corresponding _<entityname>.h file.
My problem is I'm getting runtime errors when using any of the convenience accessors. For example, for an entity with attribute of type Integer32 called myInteger, the class file property is NSNumber. Mogenerator creates an accessor named myIntegerValue. The errors I'm getting are that myIntegerValue is an unrecognized selector for SPManagedObject. I can work around this obviously but it seems to be indicative of a bigger problem. Any ideas? Thanks!
This kind of problem is most often the result of failing to configure the class name in the Core Data model editor. You may have a class Foo and an entity Foo but they don't automatically go together (they're not required to have the same name). If you don't do this, you get instances of NSManagedObject instead of instances of your subclass. And of course, NSManagedObject doesn't have those methods.
Select the entity in the model editor and look in the model inspector on the right. Make sure that the class name is configured.
org.openxava.util.XavaException:cannot add new element in collection because an entity property is readonly..but i did not put a property readonly inside my entity class.
IT WAS A TYPE MISTMATCH PROBLEM. WHEN I LOOKED CLOSELY TO THE CODE AND PUT THE CORRECT TYPE FOR THE ENTITY PROPERTY...IT WORKS!!! IM ABLE TO ADD ELEMENT TO THE COLLECTION.
I'd like to place some custom methods directly into each NSManagedObject. Think, "calculated fields": these methods provide read-only calculated values based upon persistent values on the Entity - which is identical to this question.
I'm using the Xcode New File... Wizard to create NSManagedObject subclasses for each of my Entities. I'm trying to benefit from the system auto-creating the accessors for me. For example:
Core Data Entity: "Site"
#interface Site : NSManagedObject
As I continue to add new Attributes to my Entities, I'm replacing their corresponding NSManagedObjects by using the Wizard. When each file is replaced, my custom methods are lost.
Should I create my custom methods elsewhere so that I can continue to leverage the Wizard? Or, should I keep the methods on the NSManagedObject and add Accessors for new Attributes manually? Is there another approach?
Create a category on your NSManagedObject subclass:
In the "New File ..." dialog, choose "Objective-C category".
Create a category "CustomMethods" on "Site".
Xcode will create files Site+CustomMethods.h, declaring the #interface Site (CustomMethods), and Site+CustomMethods.m for the corresponding implementation.
Add your custom methods to the category.
These files will not be overwritten when you recreate Site.m and Site.h in Xcode.
All category methods can be used as if they had been declared in the class itself. The only thing you can not do in a category is add new instance variables.
Once I have used the wizard to create the initial managed objects, I generally change them manually.
Another way of doing this is to create subclasses of the wizard generated class files and use these.
When they are regenerated, all of your custom code is in the subclass, as opposed to the overwritten class file.