Why doesn't my transformable Core Data attribute use my own NSValueTransformer?

I have a Core Data application with a fairly simple data model. I want to be able to store NSImage instances in persistent storage as PNG Bitmap NSData objects to save space.

To this end, I wrote a simple NSValueTransformer to convert NSImage to NSData in PNG raster format. I register a value transformer with this code in my application file:

+ (void)initialize { [NSValueTransformer setValueTransformer:[[PNGDataValueTransformer alloc] init] forName:@"PNGDataValueTransformer"]; } 

In my data model, I set the Transformable image attribute and specified PNGDataValueTransformer as the name of the value converter.

However, my custom value converter is not used. I know this because I posted the log messages in my methods for converting the values -transformedValue: and -reverseTransformedValue , which are not logged, and the data that is stored on disk is simply an archived NSImage, not the PNG NSData object that it should be.

Why is this not working?

Here is the code of my value transformer:

 @implementation PNGDataValueTransformer + (Class)transformedValueClass { return [NSImage class]; } + (BOOL)allowsReverseTransformation { return YES; } - (id)transformedValue:(id)value { if (value == nil) return nil; if(NSIsControllerMarker(value)) return value; //check if the value is NSData if(![value isKindOfClass:[NSData class]]) { [NSException raise:NSInternalInconsistencyException format:@"Value (%@) is not an NSData instance", [value class]]; } return [[[NSImage alloc] initWithData:value] autorelease]; } - (id)reverseTransformedValue:(id)value; { if (value == nil) return nil; if(NSIsControllerMarker(value)) return value; //check if the value is an NSImage if(![value isKindOfClass:[NSImage class]]) { [NSException raise:NSInternalInconsistencyException format:@"Value (%@) is not an NSImage instance", [value class]]; } // convert the NSImage into a raster representation. NSBitmapImageRep* bitmap = [NSBitmapImageRep imageRepWithData: [(NSImage*) value TIFFRepresentation]]; // convert the bitmap raster representation into a PNG data stream NSDictionary* pngProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:NSImageInterlaced]; // return the png encoded data NSData* pngData = [bitmap representationUsingType:NSPNGFileType properties:pngProperties]; return pngData; } @end 
+10
objective-c image cocoa core-data
Oct 21 '09 at 3:34
source share
6 answers

It turns out that this is actually a mistake within the framework. See this post from an Apple employee on the Cocoa -Dev mailing list:

http://lists.apple.com/archives/Cocoa-dev/2009/Dec/msg00979.html

+2
Dec 17 '09 at 1:03
source share

If I am not mistaken, your value transformer has the opposite direction. I am doing the same in my application using the following code:

 + (Class)transformedValueClass { return [NSData class]; } + (BOOL)allowsReverseTransformation { return YES; } - (id)transformedValue:(id)value { if (value == nil) return nil; // I pass in raw data when generating the image, save that directly to the database if ([value isKindOfClass:[NSData class]]) return value; return UIImagePNGRepresentation((UIImage *)value); } - (id)reverseTransformedValue:(id)value { return [UIImage imageWithData:(NSData *)value]; } 

While this is for the iPhone, you should be able to change your NSImage code in the appropriate places. I just have not tested the Mac implementation yet.

All I did to enable this was to change the image property in the data model and specify the name of the transformer. I did not need to manually register the value converter, as in the + initialize method.

+25
Oct 21 '09 at 15:17
source share

It seems that registering a transformer does not affect whether Core Data will use it or not. I played with the sample code PhotoLocations, and uninstalling the transformer is not affected. As long as your ValueTransformerName is the name of your class and that your implementation of the transformer is included in the target, it should work.

If there is no code execution in the transformer, then the code in the transformer does not matter. The problem should be somewhere else in the master data stack or in the transformer declaration.

+2
Nov 09 '09 at 15:10
source share

You need to explicitly register your transformer at run time.

A good place to do this is to override the class initialization method in a subclass of the NSManagedObject. As mentioned earlier, this is a known Core Data error. Below is the key code from an example Apple location code, it is tested and works: http://developer.apple.com/library/ios/#samplecode/Locations/Introduction/Intro.html

 + (void)initialize { if (self == [Event class]) { UIImageToDataTransformer *transformer = [[UIImageToDataTransformer alloc] init]; [NSValueTransformer setValueTransformer:transformer forName:@"UIImageToDataTransformer"]; } } 
+1
Sep 13 '12 at 11:23
source share

If nothing in your code elsewhere uses the PNGDataValueTransformer class, then the + initialize method for that class will never be called. Specifying a name in your Core Data model will also not start it - just try to find a value transformer for that name, which will return nil, because the transformer instance is not registered under that name yet.

If this is really what happens in your case, just add a call to [PNGDataValueTransformer initialize] somewhere in your code before your data model gets access, for example. in the + initialize method of any class, it uses this data model. This should initiate the creation and registration of the instance of the value converter so that Core Data can access it when it needs to.

0
Oct 21 '09 at 5:02
source share

Check out the sample code here .

Does it do what you want?

 @implementation UIImageToDataTransformer + (BOOL)allowsReverseTransformation { return YES; } + (Class)transformedValueClass { return [NSData class]; } - (id)transformedValue:(id)value { NSData *data = UIImagePNGRepresentation(value); return data; } - (id)reverseTransformedValue:(id)value { UIImage *uiImage = [[UIImage alloc] initWithData:value]; return [uiImage autorelease]; } 
0
Nov 24 '09 at 21:28
source share



All Articles