I'm still relatively new to Core Data, so I'm plodding through a variety of scenarios that are causing stumbling blocks. I believe I have something figured out for this particular case, so this is mostly seeking confirmation of the approach.
Sample from SlickShopper:
1. List of Products
2. ---> tap to see Product detail. Products have a Category relationship.
3. -------> tap to see Category list
When the user goes to the Product view, I want everything that they do to the Product
to be undo-able. And by this, I simply mean that they can hit cancel, and nothing is changed. However, I want anything they do to Categories - add, delete, etc - to be permanent.Pre-Core Data solution:
Make a copy of the Product object, and keep a reference to the original. User goes on about their business making changes to the copy. When it is time to save the Product detail, I manually map the various properties back onto the original object. If the user cancels, I simply ditch the copy. The Categories view does it's own thing, saving as necessary. This works just fine.
NSCopying takes care of the first part, but the saving step is manual. So there are a couple opportunities for me to forget to include a new property, which means the saving may not catch everything if I'm not careful. But otherwise, downstream VCs are none the wiser. They get a Product object, and that's all they care about; they don't care if it is the original or the copy.Core Data Solution #1:
I'm not proud of this one, but it works. I make a dummy Product object, with a flag that keeps it from appearing in any Product lists. This dummy Product takes the place of what I was doing with the copy before. So I manually map properties from the original to the dummy, the detail view is driven off the dummy, and then if they save I manually map back from the dummy to the original.
Same idea as the previous solution, but a smidge less elegant since NSCopying and NSManagedObjects don't seem to play too nicely.Core Data Solution #2:
When you initWithEntity:insertIntoManagedObjectContext:, you can provide nil for the context parameter. This will keep it out of the context until it is time to save, at which point it must be inserted into the context. This might have been a decent alternative to my dummy object from the previous solution, with one key exception:
You cannot create relationships between items in different contexts.
So my brand new Product entity has a context of nil, the Category entity I'd like to link to has a context that is non-nil, thus they are different, and this will crash when making the attempt.
So this one mostly isn't a solution in this particular case, though there will be occasions where it is handy.Core Data Solution #3
Pretty much everything you read about Core Data describes the MOC as a "scratch pad". Do whatever you want. If you want to keep it, save the MOC. If you don't, ditch it. And although I'm not looking at a multi-threading case where a 2nd MOC would be required, it seems like a reasonable enough approach.
So the basic approach I'm doing is to create a 2nd MOC in the product detail view. Let's call it "scratchObjectContext". I pass in my original Product object to this view controller. I then need to get ahold of this object via my scratchObjectContext, like so:
[[self scratchObjectContext] existingObjectWithID:[product objectID] error:nil]
In order to keep everything straight, I've adopted a naming convention. Regular names (product) for stuff in the primary MOC, prefixed names (scratchProduct) for stuff in the secondary MOC. So far so good, in concept I can make any changes that I want, and the save the 2nd MOC if I want to keep them.
This works great until I want to mess with Categories. If the user adds 20 Categories, I want those all saved, even if the user does ultimately cancel the changes to the Product. So I need Category stuff to be on the main MOC, and I'll leave the Product stuff on the 2nd MOC. But I may need to create a relationship between the Product and one of the Categories. Ok, a little more annoying, but I'll just do the existingObjectWithID: whenever necessary.
The situation I found myself in was with a new Product, and while viewing that new Product, the user also created a new Category. For some reason, in this scenario, the new Product wound up not being saved. I finally narrowed it down to situations involving saving the main MOC. If the user created a Category, I saved. If they deleted or renamed one, I saved. And every time that happened, it was like the Product would disappear. I couldn't quite put my finger on when or where it was happening. But if I didn't save the primary MOC, there were other issues.
On a whim, I decided to apply what is used in the multi-threaded case. There, you save the 2nd MOC, and the process of doing so posts a notification. You listen for the notification, and when you get it, you merge the changes back in to the 1st MOC. So I decided to do this in reverse: listen for the notification of the 1st MOC being saved, and merge those changes into the 2nd one. And by golly that seems to be working. (I could be misinterpreting my results here, but nothing was working until I added this listener)
So doing this same thing in Core Data has required me to add:
1. A 2nd MOC and then I must carry that object reference through to any downstream VCs
2. A notification listener
3. Numerous spots where I must reacquire references through the 2nd MOC
4. A goofy naming convention to help keep straight which objects belong to which MOC
5. Headache and stress while I figured all of this out, and I'm not certain I've actually solved it yet
Whenever I have to work this hard, I start to get suspicious that I'm doing something dumb, and fighting the framework. But I so far have not been able to identify exactly where I'm being stupid. So have I indeed landed on the correct solution and it's just harder than I think it should be, or am I missing an easier alternative?