Changing NSValueTransformer to NSSecureUnarchiveFromDataTransformer for Core Data warning

313 views Asked by At

In my Core Data schema, I have a 'transformable' attribute in an entity, which is using a NSValueTransformer, the purpose of which is to convert a UIImage into NSData with some compression. From this attribute, I had recently started getting these warnings about using NSKeyedUnarchiveFromData:

'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release

So I read about this (link) and figured I'd need to change the NSValueTransformer to using NSSecureUnarchiveFromDataTransformer ... but after making the changes (as best as I understood them from the article) I can't get it to work, and now the app crashes when the attribute is accessed.

This is the existing NSValueTransformer:

@interface ImageToDataTransformer : NSValueTransformer {
}

@implementation ImageToDataTransformer
+ (BOOL)allowsReverseTransformation {
    return YES;
}

+ (Class)transformedValueClass {
    return [NSData class];
}

- (id)transformedValue:(id)value {
    // for our smaller views this uses much less data and makes for faster syncing
    NSData *compressedData = UIImageJPEGRepresentation(value, 0.4);
    return compressedData;
}

- (id)reverseTransformedValue:(id)value {
    UIImage *uiImage = [[UIImage alloc] initWithData:value];
    NSData *data = (NSData *) value;
    //NSLog(@"reverseTransformedValue: image size: %@", [NSByteCountFormatter stringFromByteCount:data.length countStyle:NSByteCountFormatterCountStyleFile]);
    return uiImage;
}

So I changed it to subclass from NSSecureUnarchiveFromDataTransformer instead, and added the following to the implementation:

+ (NSArray<Class> *)allowedTopLevelClasses {
    return @[[ImageToDataTransformer class]];
}

+ (void)setValueTransformer:(nullable NSValueTransformer *)transformer forName:(NSValueTransformerName)name {
    NSLog(@"ImageToDataTransfer: calling setValueTransformer");
    [NSValueTransformer setValueTransformer:transformer forName:name];
}

+ (NSArray<NSValueTransformerName> *)valueTransformerNames {
    return @[@"ImageToDataTransformerName"];
}

Then, before the Core Data persistent store is accessed, I 'register' the transformer:

[ImageToDataTransformer setValueTransformer: [[ImageToDataTransformer alloc] init] forName:@"ImageToDataTransformerName"];

Now the warning is gone, but the app crashes when the image is read:

-[__NSCFData _rasterizedImage]: unrecognized selector sent to instance 0x7ff53c108800

If I change the transformer back to NSValueTransformer, it works fine. So I'm not sure if I'm missing something from the implementation, or I have misunderstood the premise of 'NSSecureUnarchiveFromDataTransformer'. Would love to know what I can do to fix this.

1

There are 1 answers

0
Dalmazio On

I don't think you need to override the +valueTransformerNames and the +setValueTransformer:forName: class methods.

It seems from the documentation that they should not be overriden, since the first class method will return the value transformer names that are actually registered at the time of the call. And the second method is how you actually register your value transformer. So you should remove these two overriden methods.

You can register your value transformer in the +load class method of your value transformer before anything else happens in your app, as follows:

NSValueTransformerName const ImageToDataTransformerName = @"ImageToDataTransformer";

+ (void)load
{
    [ImageToDataTransformer setValueTransformer:[[ImageToDataTransformer alloc] init]
                                        forName:ImageToDataTransformerName];
}

Then you override +allowedTopLevelClasses to return the allowed top-level transformable classes, which is just the UIImage class as follows:

+ (NSArray<Class> *)allowedTopLevelClasses
{
    return @[[UIImage class]];
}

Then in your Core Data schema put UIImage as the custom class for the transformable attribute, and ImageToDataTransformer as the value transformer name.

Let us know if it works.