Почему мой трансформируемый атрибут Core Data не использует мой собственный NSValueTransformer?

У меня есть приложение Core Data с довольно простой моделью данных. Я хочу иметь возможность хранить экземпляры NSImage в постоянном хранилище в виде объектов PND Bitmap NSData, чтобы сэкономить место.

С этой целью я написал простой NSValueTransformer для преобразования NSImage в NSData в растровом формате PNG. Я регистрирую преобразователь значения с помощью этого кода в моем делегате приложения:

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

В моей модели данных я установил атрибут изображения как Transformable, и указал PNGDataValueTransformer как имя преобразователя значения.

Тем не менее, мой пользовательский преобразователь значения не используется. Я знаю это, поскольку я разместил сообщения журнала в моем преобразователе значения -transformedValue: а также -reverseTransformedValue методы, которые не регистрируются, и данные, которые сохраняются на диск, представляют собой просто архивированный NSImage, а не объект PNG NSData, каким он должен быть.

Почему это не работает?

Вот код моего преобразователя значения:

@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

6 ответов

Решение

Оказывается, это на самом деле ошибка в рамках. Смотрите этот пост сотрудника Apple в списке рассылки Cocoa-Dev:

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

Если я не ошибаюсь, ваше значение преобразователя имеет обратное направление. Я делаю то же самое в своем приложении, используя код, подобный следующему:

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

Хотя это для iPhone, вы должны иметь возможность поменять код NSImage в соответствующих местах. Я просто еще не тестировал свою реализацию Mac.

Все, что я сделал, чтобы включить это, - установил, что мое свойство изображения должно преобразовываться в модели данных, и указал имя преобразователя. Мне не нужно было вручную регистрировать преобразователь значения, как вы делаете это в вашем методе + initialize.

Кажется, что регистрация трансформатора не влияет на то, будут ли Core Data использовать это или нет. Я играл с примером кода PhotoLocations и удаление регистрации трансформатора не имеет никакого эффекта. Пока ваше ValueTransformerName является именем вашего класса и что реализация вашего преобразователя включена в цель, она должна работать.

Если в преобразователе нет выполнения кода, то код в преобразователе не имеет значения. Проблема должна быть где-то еще в основном стеке данных или в объявлении преобразователя.

Вы должны явно зарегистрировать свой преобразователь во время выполнения.

Хорошее место для этого - переопределить метод инициализации класса в вашем подклассе сущности NSManagedObject. Как упоминалось ранее, это известная ошибка Core Data. Ниже приведен важный код из примера кода местоположения Apple, он протестирован и работает: http://developer.apple.com/library/ios/

+ (void)initialize {
    if (self == [Event class]) {
        UIImageToDataTransformer *transformer = [[UIImageToDataTransformer alloc] init];
        [NSValueTransformer setValueTransformer:transformer forName:@"UIImageToDataTransformer"];
    }
}

Проверьте пример кода здесь.

Делает ли это то, что вы хотите?

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

Если в вашем коде в другом месте ничего не используется explicity, используется класс PNGDataValueTransformer, то метод +initialize для этого класса никогда не будет вызываться. Указание имени в вашей модели базовых данных также не вызовет его - оно просто попытается найти преобразователь значения для этого имени, который вернет nil, поскольку ни один экземпляр преобразователя еще не был зарегистрирован под этим именем.

Если это действительно то, что происходит в вашем случае, просто добавьте вызов [PNGDataValueTransformer initialize] где-то в вашем коде до того, как ваша модель данных будет доступна, например, в методе +initialize любого класса, который использует эту модель данных. Это должно инициировать создание и регистрацию экземпляра преобразователя значений, чтобы базовые данные могли получить к нему доступ, когда это необходимо.

Другие вопросы по тегам