Core Data - Binary datastores with transformable attributes will not open

I store colours in my core data model using transformable attributes. Starting iOS11, this no longer works and I get an error like :


... returned error Error Domain=NSCocoaErrorDomain Code=259 "The file couldn’t be opened because it isn’t in the correct format." UserInfo={NSUnderlyingException=Can't read binary data from file, NSUnderlyingError=0x60000005cb00 {Error Domain=NSCocoaErrorDomain Code=259 "The file “<Persistent store name>” couldn’t be opened because it isn’t in the correct format." UserInfo={NSFilePath=<Path to persistent store>, NSUnderlyingException=value for key 'NS.objects' was of unexpected class 'UIColor'. Allowed classes are '{(
    NSDictionaryMapNode,
    NSSet,
    NSDictionary,
    NSOrderedSet,
    NSDecimalNumber,
    NSUUID,
    NSNumber,
    NSNull,
    NSData,
    NSDate,
    NSURL,
    NSString,
    NSArray
)}'.}}} with userInfo dictionary {
    NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=259 \"The file \U201cEmpty.rag\U201d couldn\U2019t be opened because it isn\U2019t in the correct format.\" UserInfo={NSFilePath=<Path to persistent store>, NSUnderlyingException=value for key 'NS.objects' was of unexpected class 'UIColor'. Allowed classes are '{(\n    NSDictionaryMapNode,\n    NSSet,\n    NSDictionary,\n    NSOrderedSet,\n    NSDecimalNumber,\n    NSUUID,\n    NSNumber,\n    NSNull,\n    NSData,\n    NSDate,\n    NSURL,\n    NSString,\n    NSArray\n)}'.}";
    NSUnderlyingException = "Can't read binary data from file";
}


The code works fine in iOS 10. I basically just set the attribute to a transformable type in the object model. And specify UIColor (Or NSColor for OSX) in the class property type.


Is there a new step or will this functionality no longer be supported?

Accepted Reply

Apple got back to me, there is a workaround, or rather, they sneaked in an extra little option for the persistent store while noone was looking.


The response from Apple :


Please adopt one of the following from NSPersistentStoreCoordinator.h


Allows developers to provide an additional set of classes (which must implement NSSecureCoding) that should be used while decoding a binary store. Using this option is preferable to using NSBinaryStoreInsecureDecodingCompatibilityOption.


COREDATA_EXTERN NSString * const NSBinaryStoreSecureDecodingClasses API_AVAILABLE(macosx(10.13),ios(11.0),tvos(11.0),watchos(4.0));


Indicate that the binary store should be decoded insecurely. This may be necessary if a store has metadata or transformable properties containing non-standard classes. If possible, developers should use the NSBinaryStoreSecureDecodingClasses option to specify the contained classes, allowing the binary store to to be securely decoded. Applications linked before the availability date will default to using this option.


COREDATA_EXTERN NSString * const NSBinaryStoreInsecureDecodingCompatibilityOption API_AVAILABLE(macosx(10.13),ios(11.0),tvos(11.0),watchos(4.0));


How to implement it, it's not stated that you need to supply an NSSet but I figured it out through trial and error:


            NSError *localError;
            NSDictionary *options;
            if (@available(iOS 11.0, *)) {
                options = @{
                            NSMigratePersistentStoresAutomaticallyOption : @YES,
                            NSInferMappingModelAutomaticallyOption : @YES,
                            NSBinaryStoreSecureDecodingClasses : [NSSet setWithObjects:[UIColor class], nil]
                            };
            } else {
                options = @{
                            NSMigratePersistentStoresAutomaticallyOption : @YES,
                            NSInferMappingModelAutomaticallyOption : @YES,
                            };
              
            }
            NSPersistentStore *newStore = [self.psc addPersistentStoreWithType:NSBinaryStoreType
                                                                 configuration:@"iOS"
                                                                           URL:psURL
                                                                       options:options
                                                                         error:&localError];


And everything works again! Note that your transformable class must support secureCoding otherwise you need to use the other option

Replies

I've managed to replicate the error in a clean xcode project. The problem is related to Binary Data Stores and only occurs in iOS 11. I will file a radar.


I created a github demonstrating the problem that you can access here:


https://github.com/seriouscyrus/CoreDataTransformableAttribBug

I really need to escalate this, my app is broken on ios11 unless this is fixed. I can’t migrate any models to a new format if i can’t even open the datastores.


I think my project above clearly shows the bug. It only affects binary stores, i tried the default sql backed store and it works with what I’m trying to do. The documents make no mention of the functionality being deprecated, in fact, they make specific mention of using a transformable attribute to store a color.


I Submitted a bug to Apple but have heard nothing back, and the ios11 release draws ever nearer.

Did you find a workaround / answer for this? We're running into it on 10.13 as well, and it's a total blocker for us...

Not yet, no feedback from Apple apart from a reply to my TSI which said no workaround possible.


The known issues in the release notes for the latest Beta releases make no mention of it, it's still a bug in the relased SDK.


It really infuriates me. It's obviously a serious bug that involves data loss for customers.


Apple's bug reporting process is useless, not even a note saying someone has looked at it, and developers cannot use the latest SDK without losing customer data.


I'd suggest filing a bug too. This doesn't work in the latest releases of MacOS and iOS, and it applies to *any* transformable attributes of any type, UIColor is was just how I came across it.

Maybe they'll mark it as a duplicate, but I raised another bug, stressing any transformable attribute, that it's iOS and MacOS, involves dataloss for customers and that existing apps can't be upgraded (34955643).


I would suggest other users coming across this bug raise their own bug with Apple because they seem to be ignoring me.

Apple got back to me, there is a workaround, or rather, they sneaked in an extra little option for the persistent store while noone was looking.


The response from Apple :


Please adopt one of the following from NSPersistentStoreCoordinator.h


Allows developers to provide an additional set of classes (which must implement NSSecureCoding) that should be used while decoding a binary store. Using this option is preferable to using NSBinaryStoreInsecureDecodingCompatibilityOption.


COREDATA_EXTERN NSString * const NSBinaryStoreSecureDecodingClasses API_AVAILABLE(macosx(10.13),ios(11.0),tvos(11.0),watchos(4.0));


Indicate that the binary store should be decoded insecurely. This may be necessary if a store has metadata or transformable properties containing non-standard classes. If possible, developers should use the NSBinaryStoreSecureDecodingClasses option to specify the contained classes, allowing the binary store to to be securely decoded. Applications linked before the availability date will default to using this option.


COREDATA_EXTERN NSString * const NSBinaryStoreInsecureDecodingCompatibilityOption API_AVAILABLE(macosx(10.13),ios(11.0),tvos(11.0),watchos(4.0));


How to implement it, it's not stated that you need to supply an NSSet but I figured it out through trial and error:


            NSError *localError;
            NSDictionary *options;
            if (@available(iOS 11.0, *)) {
                options = @{
                            NSMigratePersistentStoresAutomaticallyOption : @YES,
                            NSInferMappingModelAutomaticallyOption : @YES,
                            NSBinaryStoreSecureDecodingClasses : [NSSet setWithObjects:[UIColor class], nil]
                            };
            } else {
                options = @{
                            NSMigratePersistentStoresAutomaticallyOption : @YES,
                            NSInferMappingModelAutomaticallyOption : @YES,
                            };
              
            }
            NSPersistentStore *newStore = [self.psc addPersistentStoreWithType:NSBinaryStoreType
                                                                 configuration:@"iOS"
                                                                           URL:psURL
                                                                       options:options
                                                                         error:&localError];


And everything works again! Note that your transformable class must support secureCoding otherwise you need to use the other option