18
Feb
2010
 

Creating a NSManagedObject that is Cross Platform

by Marcus Zarra

An interesting question came up on Stackoverflow today so I decided to expound upon it in a short blog post.

A situation that I believe we are going to be seeing more and more often is one where application developers are writing multiple “versions” of their applications to be used on the desktop, their iPhone and now the iPad.

Because of that situation, it is becoming even more important that we write as much portable code as possible. Fortunately, our model can be completely portable between the two platforms.

## NSImage vs UIImage

The primary issue with creating a portable model and model objects is images. Now, if the images are stored on disk and only referenced in your Core Data model, then you don’t have an issue. Since you are storing the images on disk in a portable format (png, jpeg, etc.) then they will port right over to all of the platforms you are targeting.

But what happens when you are working with small images that should be stored within Core Data? Simple right, just use a transformable data type and stick the image right in there.

Unfortunately that fails in the portability department.

The issue is that on the desktop you are storing a serialized version of the NSImage instance and on the other devices you are storing an instance of UIImage. Two different data structures that are incompatible. So what is the right answer?

Store the images in a portable format even in Core Data.

But that is hard right?

Fortunately if we create concrete subclasses of any entity that needs to store images it is a small amount of code and some conditional compiling.

### `MyEntityWithAnImage.h`


@interface MyEntityWithAnImage : NSManagedObject
{
}

#ifdef IPHONEOS_DEPLOYMENT_TARGET
@property (nonatomic, retain) UIImage *image;
#else
@property (nonatomic, retain) NSImage *image;
#endif

@end

In the header we declare the same property and the use some conditional compiling to determine which definition should be used. On the desktop the property image will return an NSImage and on the other platforms it will return a UIImage. But how do we work this magic?

### `MyEntityWithAnImage.m`

#ifdef IPHONEOS_DEPLOYMENT_TARGET

- (void)setImage:(UIImage*)image
{
  [self willChangeValueForKey:@"image"];

  NSData *data = UIImagePNGRepresentation(image);
  [myManagedObject setImage:data];
  [self setPrimitiveValue:data forKey:@"image"];
  [self didChangeValueForKey:@"image"];
}

- (UIImage*)image
{
  [self willAccessValueForKey:@"image"];
  UIImage *image = [UIImage imageWithData:[self primitiveValueForKey:@"image"];
  [self didAccessValueForKey:@"image"];
  return image;
}

#else

- (void)setImage:(NSImage*)image
{
  [self willChangeValueForKey:@"image"];
  NSBitmapImageRep *bits = [[image representations] objectAtIndex: 0];

  NSData *data = [bits representationUsingType:NSPNGFileType properties:nil];
  [myManagedObject setImage:data];
  [self setPrimitiveValue:data forKey:@"image"];
  [self didChangeValueForKey:@"image"];
}

- (NSImage*)image
{
  [self willAccessValueForKey:@"image"];
  NSImage *image = [[NSImage alloc] initWithData:[self primitiveValueForKey:@"image"]];
  [self didAccessValueForKey:@"image"];
  return [image autorelease];
}

#endif

@end

Here we are overriding the dynamic accessors and implementing our own. We are fully KVO compliant and notify when we are accessing or changing the image value.

In the desktop version we grab the bitmap image representation and then get the PNG representation of it and store that representation into Core Data as NSData.

In the Cocoa Touch version we are using the C function which returns the PNG representation of the UIImage instance directly. We are again storing that as NSData into our image property.

In both cases the getter reverses the process by loading the NSData back into the appropriate image instance.

## Wrap Up

This makes the code external to our `NSManagedObject` completely unaware of the actual data storage and they just know that they are getting back the object they need to work with.

BTW, if anyone knows of another pre-processor variable that I should be using for this instead of `IPHONEOS_DEPLOYMENT_TARGET` please let me know. I am not 100% confident that `IPHONEOS_DEPLOYMENT_TARGET` is the best variable to be testing against.

Do you like this idea, hate it? Tell me in person at NSConferenceUSA! Tickets are still available and it will be great to see you there.

If that is too soon then please catch me at 360 iDev in April!

Comments

DavidM says:

The target conditionals that you are looking for are in <TargetConditionals.h>.

markaufflick says:

You could also serialise a CGImage representation, which you can turn into a UIImage or NSImage.

Having said that, I can’t think of much benefit doing it that way and ironically I suspect you would have to write more code than using PNG.

markaufflick says:

Oh, and see you next week!

Mo says:

Why don’t you define a new type so you can unify the codes? Like…

ifdef IPHONEOS_DEPLOYMENT_TARGET

typedef UIImage MYImage;

else

typedef NSImage MYImage;

NSData * UIImagePNGRepresentation(MYImage *image){
NSBitmapImageRep *bits = [[image representations] objectAtIndex: 0];

return [bits representationUsingType:NSPNGFileType properties:nil];

}

endif

@implementation MyEntityWithAnImage

  • (void)setImage:(MYImage*)image
    {
    [self willChangeValueForKey:@"image"];

    NSData *data = UIImagePNGRepresentation(image);

    [myManagedObject setImage:data];
    [self setPrimitiveValue:data forKey:@"image"];
    [self didChangeValueForKey:@"image"];
    }

  • (MYImage*)image
    {
    [self willAccessValueForKey:@"image"];
    MYImage *image = [[MYImage alloc] initWithData:[self primitiveValueForKey:@"image"]];
    [self didAccessValueForKey:@"image"];
    return image;
    }

@end

It’s easier to manage the code this way.

davbeck says:

I find that using #if TARGET_OS_IPHONE works better. The trick is that you have to user #if instead of #ifdef.

booleanman says:

For some good examples of how to write libraries that work on either OS X or iPhone OS, check out the Google Toolbox for Mac:

http://code.google.com/p/google-toolbox-for-mac/

They have a lot of sample code that will compile for either platform.

paqman says:

Hi,

I am new to iphone development and thought that your code might help me but I am having difficulty using it.

Background: I am using imagePickerController to select an image which I then pass to my bilinear scale routine, create a new UIImage. This Image is used as my background in a UIView which the user can draw lines on. As such I redraw the Image every time the user draws a line. The problem I am having is that the UImage that I create does not retain the data and crashes the program.

So I thought your code would fit into my project perfectly but I am not sure
what I am supposed to put in for myManagedObject, xcode is telling that it is undeclared.

Any help would be appreciated.
Thanks John