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

У меня есть приложение Core Data с довольно простой моделью данных. Я хочу иметь возможность хранить экземпляры NSImage в постоянном хранилище как объекты PNG 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

person Rob Keniger    schedule 21.10.2009    source источник


Ответы (6)


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

+ (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.

person Brad Larson    schedule 21.10.2009
comment
Спасибо, Брэд. Я пробовал это, и это не имело никакого значения, мой преобразователь значений просто игнорируется. - person Rob Keniger; 22.10.2009

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

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

person CornPuff    schedule 09.11.2009
comment
То же самое! Вы нашли решение? - person webmastx; 10.04.2016

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

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

person Rob Keniger    schedule 17.12.2009

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

Хорошее место для этого - переопределить метод инициализации класса в подклассе сущности NSManagedObject. Как упоминалось ранее, это известная ошибка Core Data. Ниже приведен важный код из образца кода местоположения Apple, он протестирован и работает: 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"];
    }
}
person Lukasz    schedule 13.09.2012
comment
Да, я знаю это и делаю это. - person Rob Keniger; 14.09.2012
comment
В моем случае проблема была именно в этом. Вы также можете проверить, совпадает ли имя трансформатора с тем, которое вы объявили в модели данных, и не инвертировали ли вы тела методов: transformedValue и reverseTransformedValue. - person Lukasz; 14.09.2012

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

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

person Brian Webster    schedule 21.10.2009
comment
Привет, Брайан, метод +initialize находится в моем делегате приложения, а не в преобразователе значений, и я могу определенно проверить, что он вызывается. Если я помещаю точку останова в метод -init моего NSValueTransformer, он определенно инициализируется. Если я установил для атрибута тип binary и применил свой преобразователь значений к каждой привязке в IB, он будет работать нормально. Я просто не понимаю, почему пользовательский преобразователь не используется, когда я указываю тип атрибута как Transformable. - person Rob Keniger; 21.10.2009

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

Это делает то, что вы хотите?

@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];
}
person RyanJM    schedule 24.11.2009