Can't read binary data from file

My app compiled with Xcode 9.0 on macOS 10.13 fails to open CoreData binary files with this error:


NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=259 \"The file \U201cK1GQ General Log.sl-binary\U201d couldn\U2019t be opened because it isn\U2019t in the correct format.\" UserInfo={NSFilePath=/Users/wlmyers/Documents/Ham Radio/K1GQ Station/Logs/K1GQ/K1GQ General Log.sl-binary, NSUnderlyingException=value for key 'NS.objects' was of unexpected class 'Exchange'. Allowed classes are '{(\n NSDictionary,\n NSDictionaryMapNode,\n NSArray,\n NSString,\n NSNumber,\n NSOrderedSet,\n NSCalendarDate,\n NSDate,\n NSSet,\n NSDecimalNumber,\n NSUUID,\n NSNull,\n NSURL,\n NSData\n)}'.}"


The Exchange class is of type Transformable. The file itself opens on the same macOS 10.13 machine without issues, using the same app compiled previously on macOS 10.12.


Has anyone a clue how to make Exchange an "allowed" class?

Replies

I got the exact same error with the iOS11 Beta. At the moment there doesn't seem to be any way to open Binary datastores that used a transformable attribute.


I've got customers with these out in the wild, and I can not currently use the latest SDKs.


I have raised a bug with apple : ID 33895450 but there's been no feedback on that.


I have the error on Stack overflow too, but no one has come up with a work around (I don't thing one is possible short of using the older SDK) : https://stackoverflow.com/questions/46004942/ios-11-core-data-uicolor-no-longers-works-as-transformable-attribute


I also built an example project with the bug in iOS 11, ot wouldn't be too hard to adapt it to MacOS, it's basically a stock core data template and I just changed it to use a binary store and a datamodel with a transformable attribute: https://github.com/seriouscyrus/CoreDataTransformableAttribBug


I've also used one of my TSIs from Apple which I don't think I should be using for Apple bugs, but I've had zero feedback and the latest SDK is effectively broken for me.

I got a reply from my TSI, unfortunately Apple says there's no workaround and to wait on the bug, which *still* has no comment from Apple. They refunded my TSI, that's how serious they are about no workaround.

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

Amazing! Many thanks for documenting this. It did get me past my roadblock.

Thanks! For others who come across this, i updated the github example above with a fix in a seperate branch called “appleFix” (if i paste it again i’ll get moderated) It uses newer core data APIs and the option is set with a NSPersistentStoreDescription object. It used in the lazily evaluated persistentContainer property of the appDelegate.

Stackoverflow question is also updated and someone added a swift example too. Apparently swift still needs to use an NSSet over a swift Set, but haven’t had a chance to confirm.

Thank a lot! This has helped us ship our new version. Hard to believe this is still relevant today.