Not sure my title best describes my question but couldn't think of a better one!
I have 2 Managed Objects, 'Transaction' and 'Split'. Transaction has a 1 to many relationship with 'Split' and therefore has an NSSet of Split Objects. Split has 2 properties, Category (NSString) and Amount (NSDecimalNumber).
A Transaction has a value which I currently calcluate by iterating through the NSSet of Split objects adding up all the 'Amount' properties.
This is working fine and I don't seem to have any performance issues but I suspect this wont scale well when you have 1000's of transactions.
What I think I need to do, is have an 'Amount' property on the Transaction object (as a cahce) and update this everytime and change is made to the 'Amount' property of one of its 'Split' objects contained within its NSSet.
Hope I'm making sense so far..
My question is the best way of achieving this? It feels like something KVO was designed to do but I haven't really used this before. Does my Transaction object need to be notified when the 'Amount' value of one of its Splits changes and can therefore recalculate its own value?
Or should this be done in the setter for the Amount property in the Split??
Sure this is a common problem and one for which a very elegant solution exists? Any advice and sample code very much appreciated..?
Cheers
I would go with overriding the amount setter of Split.
This way, if you some day need to optimise, or scale-up, you could change the one-to-many relationship, to a relationship with no inverse, saving the creation of the set entirely (deletion enforcement in case of removing the inverse will be more complex).
code will look something like:
//not tested
- (void) setAmount:(NSUInteger)amount
{
[self willChangeValueForKey:#"amount"];
[self setPrimitiveValue:#(amount) forKey:#"amount"];
self.transaction.amount += amount;
[self didChangeValueForKey:#"amount"];
}
Edit (#Tommy):
On deletion you will need to subtract the Split amout:
- (void) prepareForDeletion
{
self.transaction.amount -= self.amount;
}
Also, be very careful with setting your context merge policy, so changes to a transaction from different contexts will not overwrite each other (use the default error policy).
Related
I've been trying to build a simple load/save system for a game using Core Data.
Saving, loading and creating my UIManagedDocument works fine. Setting and loading values for attributes in my savegame entity works fine as well.
The problem is that these values are lost when my app quits, because I don't know how to access them.
I have about 200 attributes inside my savegame entity, such as (NSNumber *)currentIncome, NSNumber *currentMoney, NSNumber *currentMonth etc. Seems simple, right? If I were to group these into attributes with relationships, I'd probably end up with about 20 attributes. From what I gathered from the Core Data Programming Guide, in order to fill the entity with the values from my saved UIManagedDocument, I need to perform a fetchrequest with a predicate which fills an array of results.
This is where my first question arises: Would I need to perform a fetchrequest for every single attribute? This seems useful if you only have a few attributes or if you have 'to many' relationships. In my case, this would seem incredibly tedious though.
I might be missing something very essential here, but I would need something like load my UIManagedDocument and automatically fill my NSManagedModel with the one that was saved in the document's context and I cannot find it.
That is where my second question comes in: Is CoreData and UIManagedDocument even the right approach for this? 200 variables is too much for NSUserDefaults - I could imagine using NSCoding though. I definately want to incorporate iCloud savegame sharing at a later point, and UIManagedDocument and CoreData just seemed perfect for this.
Solved:
I just rewrote the entire Core Data fetching code (20 lines down to 10 or so).
Performing a fetchrequest for an entity without a predicate apparently returns the entire entity.
If my (NSArray *)fetchedResults turns up nil (database is empty), I insert a new instance of my savegame entity in my managedobjectcontext.
If it turns up non-nil, I just do a (NSManagedObject *)saveGame = [fetchedResults lastObject] and every value gets loaded fine.
From a database perspective it sounds like what you have here is a database with a single table saveGame with 200 columns currentMoney, currentMonth, etc. You then have a single row in your database representing the current game state.
The NSFetchRequest is the equivalent of the database SELECT statement, and as you only have one row you don't really need any predicates WHERE clauses etc, just get everything from this table, which is what your fetch request that only specifies the entity is doing SELECT * FROM saveGame.
So all in all it doesn't sound like you're getting much value out of the core-data framework here. Another alternative might be to look into the iCloud Key-Value storage API, which sounds closer to what you are currently getting from core-data.
Does anyone have an example of how to efficiently provide a UITableView with data from a Core Data model, preferable including the use of sections (via a referenced property), without the use of NSFetchedResultsController?
How was this done before NSFetchedResultsController became available? Ideally the sample should only get the data that's being viewed and make extra requests when necessary.
Thanks,
Tim
For the record, I agree with CommaToast that there's at best a very limited set of reasons to implement an alternative version of NSFetchedResultsController. Indeed I'm unable to think of an occasion when I would advocate doing so.
That being said, for the purpose of education, I'd imagine that:
upon creation, NSFetchedResultsController runs the relevant NSFetchRequest against the managed object context to create the initial result set;
subsequently — if it has a delegate — it listens for NSManagedObjectContextObjectsDidChangeNotification from the managed object context. Upon receiving that notification it updates its result set.
Fetch requests sit atop predicates and predicates can't always be broken down into the keys they reference (eg, if you create one via predicateWithBlock:). Furthermore although the inserted and deleted lists are quite explicit, the list of changed objects doesn't provide clues as to how those objects have changed. So I'd imagine it just reruns the predicate supplied in the fetch request against the combined set of changed and inserted records, then suitably accumulates the results, dropping anything from the deleted set that it did previously consider a result.
There are probably more efficient things you could do whenever dealing with a fetch request with a fetch limit. Obvious observations, straight off the top of my head:
if you already had enough objects, none of those were deleted or modified and none of the newly inserted or modified objects have a higher sort position than the objects you had then there's obviously no changes to propagate and you needn't run a new query;
even if you've lost some of the objects you had, if you kept whichever was lowest then you've got an upper bound for everything that didn't change, so if the changed and inserted ones together with those you already had make more then enough then you can also avoid a new query.
The logical extension would seem to be that you need re-interrogate the managed object context only if you come out in a position where the deletions, insertions and changes modify your sorted list so that — before you chop it down to the given fetch limit — the bottom object isn't one you had from last time. The reasoning being that you don't already know anything about the stored objects you don't have hold of versus the insertions and modifications; you only know about how those you don't have hold of compare to those you previously had.
I am working on a project that involves a lot of data, and at first I was doing it all in plist, and I realized it was getting out of hand and I would have to learn Core Data. I'm still not entirely sure whether I can do what I want in Core Data, but I think it should work out. I've set up a data model, but I'm not sure if it's the right way to do it. Please read on if you think you can help out and let me know if I'm on the right track. Please bear with me, because I am trying to explain it as thoroughly as I can.
I've got the basic object with attributes set up at the root level; say a person with attributes like a name, date of birth, etc. Pretty simple. You set up one entity like this "Person" in your model, and you can save as many of them as you want in your data and retrieve them as an array, right? It could be sorted based on an attribute in the Person, such as the date they were added to the database.
Now where I get a bit more confused is when I want to store several different collections of data with each person. For example a list of courses and associated test marks. In a plist I would have stored an array of dictionaries that stored this, sorted by the date assessed. The way I set this up in my data model was that I added an entity called "Tests" and a "to-many" relationship from Person to Tests, and then when I pull that I get an NSSet that I can order by a timestamp again? Is there a better way to do this?
Similarly the Person may have a set of arrays of numerical data (the kind that you could graph over time,eg. Nike+ stores your running data like distance vs time, and a person would have multiple runs associated with them, hence a set of arrays, each with their own associated date of collection). The way I set this up is a little different, with a "Runs" attribute with just a timestamp attribute, and that is connected from Person via a to-many relationship, with inverse "forPerson". Then the Runs entity is connected to another entity via a to-many relationship that has attributes to store numerical data and the time. This would once again I would use a time/order attribute to sort them.
So the main question I have is whether using an internal attribute like timestamp to sort a set would be the right way to load in a "array" from core data. Searching forums/stack overflow about how to store NSArrays in core data seem overly complicated compared to this, giving me the sense that I'm misunderstanding something.
Thanks for your help. Sorry for all the text, but I'm new to Core Data and I figure setting up the data model properly is essential before starting to code methods for getting/saving data. If necessary, I can set up a sample model to demonstrate this and post a picture of it.
CoreData will give you NSSets by default. These are convertible to arrays by calling allObjects or sortedArrayUsingDescriptors, if you want a sorted array. The "ordered" property on the relationship description gives you an NSOrderedSet in the managed object. Hashed sets provide quicker adds, access and membership checks, with a penalty (relative to ordered sets) for the sort.
I have an application where I pass a NSManagedObject with many (more 30) to UIViews.
I am doing it using assign.
I wonder if its is more expensive then passing a 2 or 3 properties (only the ones that the view needs) instead ?
I would love to get a clear explanation :).
Thanks
Shani
If I understand your question correctly you want to know if it more expensive to pass only the values of the NSManagedObject or the NSManagedObject itself. Objects are stored in the heap memory and are referenced by other objects as a memory address (a byte or two). It does not make duplicates of the object unless you tell it to. So if you use "strong", "assign", "retain", etc. you are not adding much memory. If you use "copy" then a new object is created and for the most part has everything in the original object copied as well. That would be expensive in terms of memory. So I think you're OK holding a reference to the NSManagedObject in each of the UIView without too much worry.
HTH
I'm displaying objects stored in Core Data in a UITableView and am having problems sorting these objects by one of the object's transformable attributes. I should point out that I'm using an NSFetchedResultsController as the controller between the Core Data store and my table view. When I was simply using an array to hold all of my objects, I could sort them without any problems at all. I'm using an FRC because I need the data grouped in sections with section headers and the FRC makes that very easy.
Let's call these objects I'm sorting "Measurement" objects. Each Measurement object has a distance attribute. That distance attribute is of a custom class, EPHDistance, so it's set up in the Core Data model as a Transformable attribute.
To make a long story short, the sorting of Measurement objects by their distance does work, but only after I've edited an object that's stored by Core Data or if I add a new object to the store. After editing the store and returning to my table that lists all the Measurement objects in order, everything works great. It's just the initial launch and viewing of the table view where the objects aren't sorted properly. I've actually placed an NSLog statement in my EPPDistance -compare: method and it's not getting called when I sort the objects until I add/edit an object in the Core Data store. For what it's worth, if I sort theses Measurement objects by their "date" attribute, which is an NSDate, it works great right out of the gate.
I'm not super experienced with Core Data and this is my first real attempt at using an NSFetchedResultsController so I'm a little baffled by this. Any input would be greatly appreciated.
Thanks a lot,
Erik
You could create an optional method in your Measurement class call -(NSString*)distanceCompareString, which returns a string that will help you sort from your EPHDistance object. The in your NSSortDescriptor, you just use distanceCompareString as your sort key.