¿Por qué mi atributo transformable Core Data no usa mi NSValueTransformer personalizado?

Tengo una aplicación Core Data con un modelo de datos bastante simple. Quiero poder almacenar instancias de NSImage en la tienda persistente como objetos PNG Bitmap NSData, para ahorrar espacio.

Con este fin, escribí un NSValueTransformer simple para convertir un NSImage a NSData en formato de bitmap PNG. Estoy registrando el transformador de valor con este código en mi delegado de la aplicación:

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

En mi modelo de datos, configuré el atributo de imagen para que sea Transformable, y especifiqué PNGDataValueTransformer como el nombre del transformador de valor.

Sin embargo, mi transformador de valor personalizado no se está utilizando. Lo sé ya que he colocado mensajes de registro en los métodos transform -transformedValue: y -reverseTransformedValue mi value transformer que no se registran, y los datos que se guardan en el disco son solo un NSImage archivado, no el objeto PNG NSData que debería ser .

¿Por qué esto no funciona?

Aquí está el código de mi transformador de valor:

 @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 

Si no me equivoco, su transformador de valor tiene una dirección inversa. Hago lo mismo en mi aplicación, usando un código como el siguiente:

 + (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]; } 

Si bien esto es para el iPhone, debería poder intercambiar su código NSImage en las ubicaciones apropiadas. Simplemente, aún no he probado la implementación de mi Mac.

Todo lo que hice para habilitar esto fue establecer que mi propiedad de imagen fuera transformable dentro del modelo de datos y especificar el nombre del transformador. No necesité registrar manualmente el transformador de valor, como lo hace en su método de + inicialización.

Parece que registrar el transformador no tiene ningún efecto sobre si Core Data lo usará o no. He estado jugando con el código de muestra de PhotoLocations y eliminar el registro del transformador no tiene ningún efecto. Siempre que su ValueTransformerName sea el nombre de su clase, y que la implementación de su transformador esté incluida con el objective, debería funcionar.

Si no hay ejecución de código en el transformador, entonces el código en el transformador es irrelevante. El problema debe estar en otro lugar en la stack de datos del núcleo, o en la statement del transformador.

Resulta que esto es realmente un error en los marcos. Vea esta publicación de un empleado de Apple en la lista de correo de Cocoa-Dev:

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

Necesita registrar explícitamente su transformador durante el tiempo de ejecución.

Un buen lugar para hacer esto es anular el método de inicialización de Clase en su subclase de entidad NSManagedObject. Como se mencionó anteriormente, se conoce la falla de Core Data. A continuación se muestra el código crucial del ejemplo de código de ubicación de Apple, se prueba y funciona: 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"]; } } 

Si nada en su código en otro lugar explicity usa la clase PNGDataValueTransformer, entonces nunca se llamará al método + initialize para esa clase. Especificar el nombre en su modelo de Datos Básicos tampoco lo activará; simplemente intentará buscar un transformador de valor para ese nombre, que devolverá cero, ya que aún no se ha registrado ninguna instancia de transformador con ese nombre.

Si esto es realmente lo que sucede en su caso, simplemente agregue una llamada a [PNGDataValueTransformer initialize] en algún lugar de su código antes de acceder a su modelo de datos, por ejemplo, en el método + initialize de cualquier clase que use este modelo de datos. Esto debería desencadenar la creación de la instancia del transformador de valor y registrarla para que Core Data pueda acceder a ella cuando lo necesite.

Vea el código de ejemplo aquí .

¿Hace esto lo que quieres?

 @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]; }