views:

475

answers:

2

I have a data model that contains several entities, each with several different attributes that store image data. These will all be small images and I need to store them in the persistent store rather than as external files.

While I can just store the image data in a Binary or Transformable attribute, it's quite likely that the user will specify identical images for two or more of these attributes, so I'd rather store one copy of each unique images rather than duplicating the image data.

I have messed around with creating an "ImageBlob" entity to store the image data and using relationships to do this but I'm a newbie with Core Data and it is not immediately apparent to me if this is the right way to go. In particular, how do I deal with the following situations?

  • I want all of my image attributes in multiple entities to use the same "image data store" so that only one instance of each image blob is stored
  • I need to ensure that if no objects are using an image in the data store that it is removed

What would be the best way of handling this?

+2  A: 

My first question would be how do you plan to identify when two objects are using the same image? Is there a property on the image you can store and query to determine whether the image you're setting already exists? And how expensive, computationally, is that? If it takes a lot of time, you might end up optimizing for storage and impacting performance.

However, if you do have a way of doing this efficiently, you could create an ImageBlob entity to do what you describe. The entity that uses ImageBlobs should have an imageBlob or imageBlobs relationship with ImageBlob. ImageBlob should have an inverse relationship with a name like, for example, users.

In your code, when you want to reuse an ImageBlob, it's as simple as doing something like this:

NSManagedObject *blob = // get the image blob
NSManagedObject *user = // get the user
[user setValue:blob forKey:@"imageBlob"]; // do this if it uses a single image
[[user mutableSetValueForKey:@"imageBlobs"] addObject:blob]; // do this if it uses multiple images

Another consideration you'll want to think about is what to do with blobs that are no longer needed. Presumably, you want to drop any images that aren't being used. To do this, you can sign up your application delegate or NSPersistentDocument subclass (depending on whether your app is document-based or not) for the NSManagedObjectContextObjectsDidChangeNotification notification. Whenever the managed object context changes, you can delete any unneeded images like this:

- (void)managedObjectContextObjectsDidSave:(NSNotification *)notification {
    NSManagedObjectContext *managedObjectContext = [notification object];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntity entityWithName:@"ImageBlob" inManagedObjectContext:managedObjectContext]];
    [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"users.@count == 0"];
    NSArray *unusedBlobs = [managedObjectContext executeFetchRequest:fetchRequest error:nil]; // Don't be stupid like me; catch and handle the error
    [fetchRequest release];

    for (NSManagedObject *blob in unusedBlobs) {
        [managedObjectContext deleteObject:blob];
    }
}
Alex
Thanks, that's a helpful suggestion. It's not a complete solution but I'm accepting the answer.
Rob Keniger
A: 

You could add a unique property called md5 to the Image Entity to make sure you're only storing the same images once.

As for the Core Data stuff, I think something like this might work: Then, make an abstract parent Entity (Parent). Add a relationship from Parent to Image called image, and set "Cascade" for the deletion method so that when you delete Parent, Image is also deleted. Add a relationship from Image to Parent called parent, or whatever, and set "Nullify" for the deletion method so that when you delete Image, the image for Parent is set to nil. Then, add your other entities and set their parent to Parent.

MattDiPasquale