Since you must deal with history, and because the events are generated manually by end users, consider changing the approach: rather than storing the current view of the model entities (i.e. recipes, ingredients, and the connections among them) store the individual events initiated by the user. This is called Event Sourcing.
The idea is to record what user does, rather than recording the new state after the user's action. When you need to get the current state, "replay" the events, applying the changes to in-memory structures. In addition to letting you implement the immediate requirements, this would let you restore the state as of a specific date by "replaying" the events up to a certain date. This helps with audits.
You can do it by defining events like this:
CreateIngredient - Adds new ingredient, and gives it a unique ID.
UpdateIngredient - Changes an attribute of an existing ingredient.
DeleteIngredient - Deletes an ingredient from the current state. Deleting an ingredient deletes it from all recipes and recipe histories.
CreateRecipe - Adds a new recipe, and gives it a unique ID.
UpdateRecipeAttribute - Changes an attribute of an existing recipe.
AddIngredientToRecipe - Adds an ingredient to an existing recipe.
DeleteRecipe - Deletes a recipe.
CreateRecipeHistory - Creates a new recipe history from a specific recipe, and gives the history a new ID.
UpdateRecipeHistoryAttribute - Updates an attribute of a specific recipe history.
You can store the individual events in a single table using Core Data APIs. Add a class that processes events in order, and creates the current state of the model. The events will come from two places - the event store backed by Core Data, and from the user interface. This would let you keep a single event processor, and a single model with the details of the current state of recipes, ingredients, and recipe histories.
No, that is not what happens: you read the whole history on start-up into the current "view", and then you send the new events both to the view and to the DB for persistence.
When users need to consult the history (specifically, when they need to find out how the model looked as of a specific date in the past) you need to replay the events partially, up until the date of interest.
Since the events are generated by hand, there wouldn't be too many of them: I would estimate the count in the thousands at the most - that's for a list of 100 recipes with 10 ingredients each. Processing an event on a modern hardware should be in microseconds, so reading and replaying the entire event log should be in the milliseconds.
Furthermore, do you know any link that shows an example of how to use Event Sourcing in a Core Data application? [...] For example, should I need to get rid of RecipeHistory NSManagedObject?
I do not know of a good reference implementation for event sourcing on iOS. That wouldn't be too different from implementing it on other systems. You would need to get rid of all tables that you currently have, replacing it with a single table that looks like this:
The attributes would be as follows:
EventId - Unique ID of this event. This is assigned automatically on insertion, and never changes.
EntityId - Unique ID of the entity created or modified by this event. This ID is assigned automatically by a Create... processor, and never changes.
EventType - A short string representing the name of this event type.
EventTime - The time the event has happened.
EventData - A serialized representation of the event - this can be binary or textual.
The last item can be replaced for a "denormalized" group of columns representing a superset of attributes used by the 12 event types above. This is entirely up to you - this table is merely one possible way of storing your events. It does not have to be Core Data - in fact, it does not even need to be in a database (although it makes things a little easier).
I'm a bit shocked about your reply. What's the advantage of that? However I've never used something similar and actually I don't even know where to start reyling on the current implementation in Core Data.
@FredCollins I've implemented temporal and bi-temporal models using both the copy-on-write approach that you are using and an event sourcing approach that I described in this answer. The event sourcing approach was significantly easier to implement correctly, and the resultant data model was much easier to maintain. Most importantly, copy-on-write approach generated a lot more duplication, and created issues when trying to audit records to see what happens when something goes wrong.
Replaying the events should happen only when the user consults the history, right? Furthermore, do you know any link that shows an example of how to use Event Sourcing in a Core Data application? That would be very appreciated for the reason that I've never used that. For example, should I need to get rid of RecipeHistory NSManagedObject ?
Altough I think I'm going to not implement this kind of answer in this application I'd like to say a big thank you and a +1 for introducing me to Event Sourcing, it seems interesting and I will try soon! Fred