iOS Core data issue - ios

I have made a many-to-many relationship. At first I insert all the data in table 1, the rest of the data isn't available right away.
When the data is available I like to connect it to the right table 1 entries. Should I query table 1 and then set the NSSet with the returned data? Or how would one do this?
To elaborate my question here the example:
[ActivityTable] <<--->> [BannerTable]
At viewDidLoad all the activity are upserted in the ActivityTable. Then the banners from the first activity (first upcoming date) is found from the server.
I got the two (it is always two) banners available but how do I set this?
Used this with help of the answer:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Banner" inManagedObjectContext:context];
Banner *banner = [[Banner alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
banner.image = shieldDictionary[BANNER_IMAGE];

Assuming you are using the generated NSManagedObject subclasses for your entities...
In your CoreData model each relationship has a name.
Your generated entity classes already contain methods to add/remove relationships you defined in the model. So all you have to do is use one of those methods.
Example:
If the tables are called 'ActivityTable' and 'BannerTable', and the relationship in 'ActivityTable' is called 'banners', then the generated methods look like:
- addBannersObject:(BannerTable *)value;
- removeBannersObject:(BannerTable *)value;
- addBanners:(NSSet *)value;
- removeBanners:(NSSet *)value;

Core Data manages a graph of objects, not tables.
The easiest way to realize many-to-many relationships is to use the dynamically-generated accessors like add<Key>Object: and remove<Key>s:.
For instance:
[anActivity addBannerObject:aBanner];
You can use the data model editor to generate class files for your models which declare these generated methods. You can also use -mutableSetValueForKey: to get a proxy that lets you add and remove objects from a relationship. i.e.:
NSMutableSet *banners = [anActivity mutableSetValueForKey:#"banners"];
[banners addObject:aBanner];
It is obviously less verbose to use the generated methods for each relationship, but it achieves the same thing.

Related

Core Data - Relationships between Multiple Core Data Models

Platform
iOS 10, Xcode 8.3.3
Background
I have built a Notes application that takes advantage of Core Data and I'd like to use this app in my next application, which will also use Core Data. For simplicity, lets call my next application, "ListApp", and my notes application, "NotesApp". This ListApp has list items each of which can have one or more notes.
Here's what I've done so far:
Removed all unnecessary files from the NotesApp and compiled a "NotesApp Framework".
Linked the NotesApp Framework to the ListApp.
Designed the Core Data Model for ListApp. Specifically, I created an entity called "ListItem" and an entity called "Note". The ListItem has a to-many relationship with the Note (one list item can have multiple notes). The Note entity contains a "noteID" field to reference the note in the NotesApp model and, of course, the inverse relationship.
Problem
I need to form a "relationship" between an entity in the ListApp model and an entity in the NotesApp model.
I've researched configurations and that seems to be more for storing objects in the same model to different persistent stores unless there's something I'm missing. So, that doesn't help.
Then, I found that fetched properties can be used to form weak relationships between multiple stores. So, that doesn't help either.
Next, I found in the documentation that there's a method called NSManagedObjectModel.mergedModel(from:) so I'm assuming this is possible. Or maybe that's only for migration?
That's where I'm stuck.
Reason
I'd rather not redesign everything in the NotesApp model in to the ListApp model. I prefer to keep everything separate.
Questions
Is there a way to form a relationship between two entities in different models? Should I just add a function in the ListItem entity class to fetch the notes in the NotesApp model manually? Am I even going down the right path or is there a better option?
NOTE: What I mean by "relationship" is the ability to call on a property in the ListItem entity to fetch the notes and somehow "relate" specific notes to a specific ListItem.
P.S. If you know of any pitfalls, have any general advice, or know of any reading material please feel free to let me know.
Also, I've been researching this topic for a couple of hours and I can't seem to find anything about it. I'm assuming that's either because it's not possible, it's a terrible practice, or I'm not using the right keywords.
If anyone needs any more information feel free to let me know!
I think you're saying your bundle will have one model that contains the List entity and another that contains the Note entity. You can merge and tweak managed object models, as you suggested, and use the resulting managed object model which you have in code.
If you're creating your Core Data stack in code (that is, you are not using the new NSPersistentContainer), it is easy to splice in a custom managed object model.
If you are using NSPersistentContainer, you'd have to subclass it and override managedObjectModel(). I can't find any documentation saying you can't do that, but I wouldn't bet on that.
If you're document-based, overriding UIDocument's managedObjectModel should work.
To create your custom managed object model, merge your models using NSManagedObjectModel.mergedModel(from:). Then, get the Note and List entity decriptions, get the properties of each, mutate, add your new relationships, then set them back into the model. You would only do this on the first run; cache your custom managed model to a ivar for subsequent runs.
Hmmmm. What I've just described, essentially tearing apart, tweaking and reassembling that managed object model, is going to be quite a few lines of code. If this is a really just a simple "notes and lists" app, and if these are the only these two entities, it would probably be less code to ditch those .mom files and create the whole managed object model from scratch, in code. It's not that hard. Put on your Objective-C glasses and look at the managedObjectModel() function in main.m of Apple's old Core Data Utility sample project.
Alright, so turns out I had a slight misunderstanding of the Core Data Stack but, this is an extremely simple task. I was able to get this to work very easily based on some research and #Jerry Krinock's answer.
Create a framework containing the needed files from the NotesApp.
Link the framework to the ListApp.
Grab mutable references to the ListApp and NotesApp NSManagedObjectModel.
Programmatically add a NSRelationshipDescription between the ListItem entity in the ListApp Model and the Note entity in the NotesApp Model (and vice versa for the inverse).
Create a NSManagedObjectModel by merging the ListApp and NotesApp models.
NOTE: As #Jerry Krinock mentioned this only needs to be done once since we are merging the two models together and storing them in the same persistent store. This is the same as doing it through the CoreData Model Builder UI except programmatically since it doesn't support referencing entities from separate models (at least not that I know of or could find).
References:
Core Data Programming Guide
Core Data stack
Universal Cocoa Touch Frameworks for iOS8 – (Remix)
Adding relationships in NSManagedObjectModel to programmatically created NSEntityDescription
Objective-C:
NSManagedObjectModel * listModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self listModelURL]] mutableCopy];
NSManagedObjectModel * notesModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self notesModelURL]] mutableCopy];
NSEntityDescription * listEntity = [listModel.entitiesByName objectForKey:NSStringFromClass([JBListItemMO class])];
// The framework name is prepended to the class name. Remove it before getting the note's entityDescription.
NSString * noteClassName = [NSStringFromClass([JPSNoteMO class]) componentsSeparatedByString:#"."].lastObject;
NSEntityDescription * noteEntity = [notesModel.entitiesByName objectForKey:noteClassName];
NSRelationshipDescription * whichListRelationship = [[NSRelationshipDescription alloc] init];
whichListRelationship.minCount = 0;
whichListRelationship.maxCount = 1;
whichListRelationship.optional = NO;
whichListRelationship.name = #"whichList";
whichListRelationship.destinationEntity = listEntity;
whichListRelationship.deleteRule = NSNullifyDeleteRule;
NSRelationshipDescription * notesRelationship = [[NSRelationshipDescription alloc] init];
notesRelationship.ordered = NO;
notesRelationship.maxCount = 0;
notesRelationship.minCount = 0;
notesRelationship.optional = YES;
notesRelationship.name = #"notes";
notesRelationship.destinationEntity = noteEntity;
notesRelationship.deleteRule = NSCascadeDeleteRule;
notesRelationship.inverseRelationship = whichListRelationship;
whichListRelationship.inverseRelationship = notesRelationship;
listEntity.properties = [listEntity.properties arrayByAddingObject: notesRelationship];
noteEntity.properties = [noteEntity.properties arrayByAddingObject: whichListRelationship];
self.managedObjectModel = [NSManagedObjectModel modelByMergingModels:#[listModel, notesModel]];
I'll post the Swift 3 code when I've finished converting my CoreDataStack class.

Avoid repeating code in NSManagedObject categories

In my entity model I have a top-level "Installation" entity, which has a child "cards" relationship. I also have a "Person" entity, which has a child "cards" relationship.
I've written some code which will sort the NSSet of cards to return a specific subset (called sortedCards), and this function can be performed at either the Installation level, or at the Person level.
For exmaple, I want to be able to call:
NSArray *cards = [installation sortedCards];
as well as:
NSArray *cards = [person sortedCards];
Where am I supposed to put this code so that I don't copy the code in two places? I started by putting it in the Installation NSManagedObject category that I created. But if I do that, i need to copy the code into the Person category as well.
Should I put in an NSSet category and call [installation.cards sortedCards] and [person.cards sortedCards]? That doesn't feel right either.
Any help much appreciated.
Duncan
You should place this method in NSSet category, because categories are used for extending basic functionality. And if you need sort NSSet in different places, it should be NSSet category work, not other object or class.

Inserting one entity into another one

I don't even know how to title this one:
Lets say I have a manufacturer entity and a model entity, with a one-to-many relationship.
Each manufacturer can have multiple models (just using these as an example).
manufacturer has a tableview and its independent fetchedResultsController, then when you press on a manufacturer cell you go to models viewcontroller that also has its own tableview and fetchedResultsController, ofc showing the relevant added models.
Let's say I would like to take one of the models and copy them or cut them into another manufacturer, I was thinking of a method styled like:
-(void)copyThis:(Model*)model toThat:(Manufacturer*)manufacturer
I am grabbing the right manufacturer object and the right model object but how can I implement the insertion of one to another?
To copy
Model *newModel = [NSEntityDescription insertNewObjectForEntityForName:#"Model" inManagedObjectContext:self.context];
newModel.property = model.property; //For every property
model.relationShipName = manufacturer;
[self.context insertObject:copyModel];
To cut
model.relationShipName = manufacturer;
(I assume that you have an xcdatamodeld and have generated an NSManagedObjectSubclass of your Model and Manufacturer entities)
What do you want to achieve with copying? Do you want a 'new' model with the exact parameters added to the other manufacturer, or do you want the relationship to be with the same model object?
Assuming you want to keep a single instance of the Model object:
Manufacturer *fromManufacturer = ...
Model *model = [[fromManufacturer models] objectAtIndex:...];
Manufacturer *toManufacturer = ...
[toManufacturer insertModelObject:model];
if (isCut) [fromManufacturer removeModelObject:model];
To get the insertModelObject and removeModelObject methods automatically, you can use Xcode to generate NSManagedObject subclasses for you automatically. It's under the Editor menu when you're looking at the CoreData Model file. Note that the names of the methods and objects may be different depending on the CoredData model structure and relationship names you've created.

Adding a relationship in core data

I have been at this single task for several days trying to get the relationships between core data entities working. I have achieved this but now I need to change it so that the new attribute value has its relationship added to an existing object. It is a 1 - to - many database.
I am not sure how to add a relationship to a object that already exists. So in the new object that is getting added to RoutineDetail, how would I create the relationship to the object that already exists in the routine Entity?
I have looked at several examples all showing how to add relationships to newly added objects but I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
The value of Routines is held in a string called RoutineText
rout is the NSmangedObject for the entity Routines
routDet is the NSmanagedObject for the entity RoutinesDetails
I have left the commented out code that allows me to add a relationship when both new objects are created.
This is the last thing I have to do in my project but it is driving me insane. I will be eternally grateful for the fix here. Any advice will be appreciated as this is the best knowledge portal there is. Thank You.
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new device
ExcerciseInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
//rout = [NSEntityDescription insertNewObjectForEntityForName:#"Routines" inManagedObjectContext:context];
routdet = [NSEntityDescription insertNewObjectForEntityForName:#"RoutinesDetails" inManagedObjectContext:context];
//Add attribute values
//[rout setValue: RoutineText forKey:#"routinename"];
[routdet setValue: info.name forKey:#"image"];
//Create Relationship
[rout addRoutinedetObject:routdet];
Your main problem statement is, I think, here:
I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
I presume your data model looks like this:
Routine <----> RoutineDetail
i.e. every one routine has one routine detail (one-to-one relationship).
But this does not really make any sense. You could simply include the attributes of RoutineDetail in the Routine entity.
Instead of
desiredValue = routineDetail.routine.value;
you would simply have
desiredValue = routineDetail.value;
Also, please note that your code has a number of problems. The first line is completely unnecessary, just use self.managedObjectContext. Additionally, against the convention you are using Capital initials for variables. Those should be reserved for class names. Your method to add the relationship also does not look right.
You can add a relationship like this, without a method call:
routineObject.detail = detailObject;

Does using a class and dot notation access in Core Data get a mutable set?

I have an entity called LogBook which has an attribute (called columns) for a set of LogBookColumn entities (one-to-many relationship).
The standard way I see to retrieve the mutable set of columns seems to be:
NSEntityDescription *myLogbook;
myLogbook = [NSEntityDescription insertNewObjectForEntityForName:#"LogBook"
inManagedObjectContext:self.managedObjectContext];
NSMutableSet *columns = [myLogbook mutableSetValueForKey:#"columns"];
Instead of method on the third line, I want to use dot notation. To do so, I have created class definitions, also called LogBook and LogBookColumn, and use #property to create the setters and getters.
LogBook *myLogBook;
myLogbook = [NSEntityDescription insertNewObjectForEntityForName:#"LogBook"
inManagedObjectContext:self.managedObjectContext];
NSMutableSet *columns = (NSMutableSet *)myLogbook.columns;
So, is columns truly a mutable set by default? I have done two things to verify:
Attempted to write to the list, eg: [columns addObject:aColumn];
Asked with: BOOL isKindOfMutableSet = [myLogbook.columns isKindOfClass:[NSMutableSet class]];
Both work with expected results, which may make this question overkill, but I am very concerned about memory errors that will be difficult to track down. I also wonder if asking the question isKindOfClass will work as I have defined this as a mutable set - so won't it work even if the underlying memory organization doesn't support mutable sets?
All of the above sums up to: is this the right way to access and change the columns property/attribute?
According to this documentation, to-many relationships should be declared as NSSet, which makes sense. Even if the attribute returns an NSMutableSet, it is not guaranteed that updating the NSMutableSet will properly update relationships (which mutableSetValueForKey: does).
If you really want a mutable accessor, then just create a readonly property that wraps mutableSetValueForKey:
Update:
Otherwise, we are supposed to use - (void)addLogBookColumnsObject:(LogBookColumn *)value;, - (void)removeLogBookColumnsObject:(LogBookColumn *)value;, - (void)addLogBookColumns:(NSSet *)values;, - (void)removeLogBookColumns:(NSSet *)values; that were generated by Core Data for us.

Resources