43

I've built a static library that makes heavy use of the Core Data framework. I can successfully use the library in my external project, but ONLY if I include the .xcdatamodel file in the main project. That is less than ideal, since the point of the library was to hide implementation details to the maximum possible.

In a separate question, I was informed that I cannot bundle resources with a library (which makes complete sense to me now).

So is there a way to programatically allow the model to be 'discovered' without having to include the model in the main project?

Community
  • 1
  • 1
Vik
  • 1,301
  • 4
  • 16
  • 29

8 Answers8

59

Sascha's answer got me on the right track. Merging a compiled .mom file from a static library into the .mom file from a host project was relatively simple. Here's a trivial example:

  1. Create a new XCode Static Library project called MyStaticLibrary

  2. Create an .xcdatamodel file in MyStaticLibrary called MyStaticLibraryModels.xcdatamodel, add some Entitys, then generate the headers and implementations. When you build the MyStaticLibrary target, you'll generate a libMyStaticLibrary.a binary file, but it won't include the compiled .mom file. For that we have to create a bundle.

  3. Create a new build target of type Loadable Bundle, found under MacOS X > Cocoa, let's call the new Target MyStaticLibraryModels.

  4. Drag MyStaticLibraryModels.xcdatamodel into the Compile Sources build phase of the MyStaticLibraryModels Target. When you build the MyStaticLibraryModels Target, you will generate a file called MyStaticLibraryModels.bundle and it will contain the compiled NSManagedObjectModel file, MyStaticLibraryModels.mom.

  5. After building both the MyStaticLibrary and MyStaticLibraryModels Targets, drag libMyStaticLibrary.a (along with any associated Model header files) and MyStaticLibraryModels.bundle into your host project, MyAwesomeApp.

  6. MyAwesomeApp uses CoreData, has it's own .xcdatamodel file which will get compiled into a .mom file during its own build process. We want to merge this .mom file with the one we imported in MyStaticLibraryModels.bundle. Somewhere in the MyAwesomeApp project, there is a method that returns MyAwesomeApps NSManagedObjectModel. The Apple generated template for this method looks like this:

...

- (NSManagedObjectModel *)managedObjectModel {
  if (managedObjectModel_ != nil) {
    return managedObjectModel_;
  }
  NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyAwesomeApp" withExtension:@"momd"];
  managedObjectModel_ = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];    
  return managedObjectModel_;
}

We will alter this to merge and return BOTH of our NSManagedObjectModels, MyAwesomApps and MyStaticLibraryModels, as a single, combined NSManagedObjectModel like so:

- (NSManagedObjectModel *)managedObjectModel {
  if (managedObjectModel_ != nil) {
    return managedObjectModel_;
  }

  NSMutableArray *allManagedObjectModels = [[NSMutableArray alloc] init];

  NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyAwesomeApp" withExtension:@"momd"];
  NSManagedObjectModel *projectManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
  [allManagedObjectModels addObject:projectManagedObjectModel];
  [projectManagedObjectModel release];

  NSString *staticLibraryBundlePath = [[NSBundle mainBundle] pathForResource:@"MyStaticLibraryModels" ofType:@"bundle"];
  NSURL *staticLibraryMOMURL = [[NSBundle bundleWithPath:staticLibraryBundlePath] URLForResource:@"MyStaticLibraryModels" withExtension:@"mom"];
  NSManagedObjectModel *staticLibraryMOM = [[NSManagedObjectModel alloc] initWithContentsOfURL:staticLibraryMOMURL];
  [allManagedObjectModels addObject:staticLibraryMOM];
  [staticLibraryMOM release];

  managedObjectModel_ = [NSManagedObjectModel modelByMergingModels:allManagedObjectModels];
  [allManagedObjectModels release];

  return managedObjectModel_;
}

This will return the merged NSManagedObjectModel with the Entitys from both MyAwesomeApp and MyStaticLibrary.

prairiedogg
  • 6,323
  • 8
  • 44
  • 52
  • I am using Xcode4 and following the steps above, I managed to create a bundle with a mom in it, included and used for creating a coordinator and a context. However, when I try to insert a new entity that I am sure it esists, I got the error message "could not locate entity named...". Also, if I try to count all the model entities, I got 0. Any guess ? – Leonardo Jun 27 '11 at 12:55
  • 1
    Good solution. The key thing that @prairedogg glosses over (and I missed) is that: Xcode will CREATE A COPY RESOURCES phase, but Xcode is hardcoded to IGNORE COPY RESOURCES PHASES IN A STATIC LIBRARY TARGET. If you include your bundle in your library - nothing will ever happen. You have to include your bundle into your final app separately (obvious when you think about it - we're working around the bug that Xcode fails to copy things its supposed to copy in the first place ;)). – Adam Nov 20 '13 at 14:50
  • 1
    Note that some of the path above is changed. You visitors should change some of the code like adding path component not just replace the string. I'm using xcode6 beta with ios8 sdk. – ManuQiao Jun 23 '14 at 03:37
  • This was the only correct answer for my situation. Xcode 5.1 targeting iOS 7.1. – AWrightIV Jul 15 '14 at 00:27
  • Here's a step-by-step tutorial of doing this in Xcode 5: http://bharathnagarajrao.wordpress.com/2014/02/14/working-with-core-data-in-a-static-library/ – Liron Yahdav Jul 31 '14 at 16:57
31

I also created my own static library that uses Core Data. Besides the static library I have a another bundle target in the project where I have a Copy Bundle Resources item, that copies some images and things like that into the bundle and a Compile Sources build phase, where I am compiling the xcdatamodel.

The final bundle will contain all the necessary files. In your main project that relies on the static library you have to include that bundle as well. Your main project will now have access to the mom file that is needed to use core data.

To use core data with the mom from the bundle you have to create a merged managed object model in your code (it might be the main project has some core data model as well):


- (NSManagedObjectModel *) mergedManagedObjectModel 
{   
    if (!mergedManagedObjectModel) 
    {
        NSMutableSet *allBundles = [[[NSMutableSet alloc] init] autorelease];
        [allBundles addObjectsFromArray: [NSBundle allBundles]];
        [allBundles addObjectsFromArray: [NSBundle allFrameworks]];

        mergedManagedObjectModel = [[NSManagedObjectModel mergedModelFromBundles: [allBundles allObjects]] retain];
    }

    return mergedManagedObjectModel;
}


By just including the bundle you will not have to give out the xcdatamodel, only the compiled mom file needs to be included.

Sascha Konietzke
  • 1,142
  • 9
  • 14
  • Sascha -- this works reasonably well. The MOM file is still readable in XCode, but at least that is better than having a nice data model diagram being displayed. – Vik Nov 19 '09 at 20:32
  • 8
    Sascha - I know it's been a while since your answer here, but I was wondering if you could elaborate on how you setup your targets and build phases so a project that uses a static library can see and include the compiled mom file. I am currently building a static framework that uses CoreData as well, and I couldn't figure out how to do this. I am using lipo to compile two separate .a files (device and simulator versions) into a single static library bundle. – Taylan Pince Nov 16 '10 at 23:08
2

Prairiedogg's answer is a little outdated, here's a tutorial on doing this in Xcode 5: http://bharathnagarajrao.wordpress.com/2014/02/14/working-with-core-data-in-a-static-library/

Liron Yahdav
  • 10,152
  • 8
  • 68
  • 104
2

Note that instead of using xcdatamodel/mom file you can also create your model in code (especially if you have a simple model) and this way you won't need to create an additional bundle for resources. Here is a simple example with one table that contains two attributes:

- (NSManagedObjectModel *)coreDataModel
{
    NSManagedObjectModel *model = [NSManagedObjectModel new];

    NSEntityDescription *eventEntity = [NSEntityDescription new];
    eventEntity.name = @"EventEntity";
    eventEntity.managedObjectClassName = @"EventEntity";

    NSAttributeDescription *dateAttribute = [NSAttributeDescription new];
    dateAttribute.name = @"date";
    dateAttribute.attributeType = NSDateAttributeType;
    dateAttribute.optional = NO;

    NSAttributeDescription *typeAttribute = [NSAttributeDescription new];
    typeAttribute.name = @"type";
    typeAttribute.attributeType = NSStringAttributeType;
    typeAttribute.optional = NO;

    eventEntity.properties = @[dateAttribute, typeAttribute];
    model.entities = @[eventEntity];

    return model;
}

Here is a tutorial about creating model from code: https://www.cocoanetics.com/2012/04/creating-a-coredata-model-in-code/

Also based on this approach I created a small and easy to use library that might fit your needs called LSMiniDB so you can check it also.

Also in my case I had warnings such as "warning: dynamic accessors failed to find @property implementation..." on the console while using properties of NSManagedObject subclasses. I was able to fix that by moving those properties to a class interface/implementation instead of having them in a category in a separate file (currently xcode by default is generating this code splited into separate files ClassName+CoreDataClass and ClassName+CoreDataProperties with a class and a category for each subclass).

Leszek Szary
  • 9,763
  • 4
  • 55
  • 62
2

i have some library with coredata too. i have found this template for manage a framework with embed ressources

it's really simple to use on a new project ( more difficult to apply on existing ) but for framewoks build, it's really cool :-)

https://github.com/kstenerud/iOS-Universal-Framework

Pixman
  • 599
  • 6
  • 17
2

Sascha Konietzke's solution works well, but there is one important caveat that needs to be provided for it to work. The bundle containing the model needs to be loaded first, otherwise it will not be included in the array and merged in the MOM.

In his case he has probably already accessed resources from the bundle therefore the bundle was already loaded prior to this code being executed.

Spanner
  • 312
  • 3
  • 11
1

No, the limitation on using non-Apple frameworks in an iPhone app really changes the dependency game relative to OS X. Most iPhone "frameworks" (e.g. Google's toolbox for Mac, Core Plot, etc.) actually recommend that you include the source in your main application project rather than linking a product (i.e. a static library). I think the community consensus is that, on iPhone, it's OK to expect consumers of your framework to have to do a little "manual" work to use your library. In your case, this is including the xcdatamodel file in the main project. As with most of Objective-C, tell your users not to make use of the implementation details and leave it at that.

Barry Wark
  • 107,306
  • 24
  • 181
  • 206
0

Swift 2 version for Sascha's answer:

lazy var managedObjectModel: NSManagedObjectModel = {
    // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
    var allBundles = NSMutableSet()
    allBundles.addObjectsFromArray(NSBundle.allBundles())
    allBundles.addObjectsFromArray(NSBundle.allFrameworks())

    let model =  NSManagedObjectModel.mergedModelFromBundles(allBundles.allObjects as? [NSBundle])

    return model!
}()
Bill Chan
  • 3,199
  • 36
  • 32