Por que meu atributo Core de dados transformável não está usando meu NSValueTransformer personalizado?

Eu tenho um aplicativo Core Data com um modelo de dados bastante simples. Eu quero ser capaz de armazenar instâncias de NSImage no armazenamento persistente como objects PNG Bitmap NSData, para economizar espaço.

Para este fim, eu escrevi um NSValueTransformer simples para converter uma NSImage para NSData no formato de bitmap PNG. Estou registrando o transformador de valor com esse código no meu delegado do aplicativo:

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

Em meu modelo de dados, configurei o atributo image como Transformável e especifique PNGDataValueTransformer como o nome do transformador de valor.

No entanto, meu transformador de valor personalizado não está sendo usado. Eu sei disso, pois coloquei as mensagens de log nos methods -reverseTransformedValue -transformedValue: do transformador de valor que não estão sendo registrados e os dados que estão sendo salvos no disco são apenas uma NSImage arquivada, não o object PNG NSData que deve ser .

Por que isto não está funcionando?

Aqui está o código do meu 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 

Se não me engano, seu transformador de valor tem uma direção inversa. Eu faço a mesma coisa no meu aplicativo, usando código como o seguinte:

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

Embora isso seja para o iPhone, você deve poder trocar seu código NSImage nos locais apropriados. Eu simplesmente ainda não testei minha implementação no Mac.

Tudo o que fiz para ativar isso foi definir minha propriedade de imagem para ser transformável no modelo de dados e especificar o nome do transformador. Eu não precisei registrar manualmente o transformador de valor, como você faz no seu método + initialize.

Parece registrar o transformador não tem efeito sobre se o Core Data vai usá-lo ou não. Eu tenho jogado com o código de exemplo PhotoLocations e remover o registro do transformador não tem efeito. Contanto que o seu ValueTransformerName seja o nome da sua class e que a implementação do seu transformador esteja incluída no destino, ele deve funcionar.

Se não houver execução de código no transformador, o código no transformador é irrelevante. O problema deve estar em algum outro lugar na pilha de core data ou na declaração do transformador.

Acontece que isso é realmente um bug nos frameworks. Veja este post por um funcionário da Apple na lista de discussão Cocoa-Dev:

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

Você precisa registrar explicitamente seu transformador durante o tempo de execução.

Um bom lugar para fazer isso é sobrescrever o método Initialize da class em sua subclass de entidade NSManagedObject. Como mencionado antes, é conhecido o bug do Core Data. A seguir está o código crucial do exemplo de código de localização da Apple, ele é testado e 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"]; } } 

Se nada em seu código explicitamente usar a class PNGDataValueTransformer, então o método + initialize para essa class nunca será chamado. Especificar o nome no seu modelo Core Data também não o acionará – ele simplesmente tentará procurar um transformador de valor para esse nome, que retornará nil, já que nenhuma instância do transformador ainda foi registrada com esse nome.

Se isto é realmente o que está acontecendo no seu caso, simplesmente adicione uma chamada a [PNGDataValueTransformer initialize] em algum lugar no seu código antes que seu modelo de dados seja acessado, por exemplo, no método + initialize de qualquer class que esteja usando este modelo de dados. Isso deve acionar a instância do transformador de valor que está sendo criada e registrada para que o Core Data possa acessá-lo quando for necessário.

Confira o código de exemplo aqui .

Isso faz o que você quer?

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