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?
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