1

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:

  1. Removed all unnecessary files from the NotesApp and compiled a "NotesApp Framework".
  2. Linked the NotesApp Framework to the ListApp.
  3. 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!

Jonathan
  • 2,623
  • 3
  • 23
  • 38

2 Answers2

2

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.

Jerry Krinock
  • 4,860
  • 33
  • 39
  • What you've suggested is sort of what I'm attempting to do. So far, I've merged the two models using the method you suggested. Then, before using the managedObjectModel, I add a fetched property on the Note entity in the ListApp model to fetch the actual note from the NotesApp model. The reason for this is that I've read that cross store relationships aren't supported. I think this will work but I haven't had a chance to test it yet. My app is much more complicated than the aforementioned but I simplified it to emphasize the problem. Thank you for the suggestions! – Jonathan Jul 07 '17 at 01:16
  • Although, after re-reading your answer there wouldn't be multiple stores as I'm merging the models.... Hmmm... – Jonathan Jul 07 '17 at 01:38
  • I got it to work. Thank you for your help! I'll accept your answer. – Jonathan Jul 07 '17 at 03:06
1

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.

  1. Create a framework containing the needed files from the NotesApp.
  2. Link the framework to the ListApp.
  3. Grab mutable references to the ListApp and NotesApp NSManagedObjectModel.
  4. 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).
  5. 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.

Jonathan
  • 2,623
  • 3
  • 23
  • 38