iOS 11 - 核心数据 - UIColor 不再用作可转换属性

iOS 11 - Core Data - UIColor no longers works as transformable attribute

我使用可转换属性将颜色存储在我的二进制核心数据存储中,将属性的 class 指定为 UIColor,如下所示:

#import "CoreDataEntity+CoreDataClass.h"
#import <UIKit/UIKit.h>


NS_ASSUME_NONNULL_BEGIN

@interface CoreDataEntity (CoreDataProperties)

+ (NSFetchRequest<CoreDataEntity *> *)fetchRequest;

@property (nullable, nonatomic, retain) UIColor *transformable;
@property (nullable, nonatomic, copy)   NSString *string;

@end

NS_ASSUME_NONNULL_END

在 iOS 11 Beta 中,它已停止工作并出现如下错误:

NSUnderlyingException=value for key 'NS.objects' was of unexpected class 'UIColor'. Allowed classes are '{(\n    NSDecimalNumber,\n    NSData,\n    NSUUID,\n    NSNumber,\n    NSDate,\n    NSArray,\n    NSOrderedSet,\n    NSDictionaryMapNode,\n    NSString,\n    NSSet,\n    NSDictionary,\n    NSURL,\n    NSNull\n)}'.}";
    NSUnderlyingException = "Can't read binary data from file";
}

I managed to replicate the specific problem in an XCode project on GitHub(必须 运行 与 XCode Beta 两次才能得到错误)。

在演示项目中,商店类型由 NSPersistentStoreDescription 控制,并将其设置为 NSBinaryStoreType,我在示例项目的 AppDelegate 中这样做,我在应用程序 didFinishLaunchingWithOptions 中添加对象,否则它是来自 iOS11 个具有核心数据的应用程序。加上一个小数据模型和 classes.

如果您 运行 项目两次,第一次创建数据存储时一切正常。第二次,数据存储尝试打开并使应用程序崩溃。据我所知,这个问题似乎只与二进制数据存储有关,如果我使用 SQL 支持的数据存储,它就可以工作。但是,我的应用程序在野外并使用二进制文件。

我已将其作为错误报告给 Apple,并在开发者论坛上寻求帮助,但 Apple 尚未确认该错误,也没有任何帮助。

随着 iOS11 发布日期的临近,我有点担心,但我没有解决方案,我的应用程序无法在 iOS11 中运行。

我已经尝试将 属性 更改为 NSData 并查看是否可以取消存档数据,但它似乎仍以某种方式在内部存储为 UIColor 并且数据库无法打开。

任何人都可以找到解决方法吗?我有这个应用程序,可能会在 iOS11 对某些人有效之前推出一个更新来转换数据存储,但这并不能保证所有用户都能得到修复,他们可能会丢失数据。

编辑 1: 雷达编号:33895450

编辑 2: 我刚刚想到这适用于核心数据中的 any 可转换属性,错误消息中支持的值只是默认的 属性 类型。

编辑 3: 出于好奇,我填写了可转换属性的所有字段(以前不需要)。 我在核心数据实体的值转换器名称中添加了 "NSKeyedUnarchiveFromData",它应该是默认值,但你永远不知道。没有效果。无论如何它必须使用值转换器才能知道它是 UIColor。 我把自定义的class字段填成了UIColor,没有效果。

编辑 5:我早些时候注意到 UIColor 现在支持 NSSecureCoding,安全性是否应该以某种方式在其他类型的商店中被忽略。

编辑:既然 iOS 已发布,我已使用我的一个 TSI 进一步升级此问题。如果我必须用一个来让他们修复他们的软件,我能把他们找回来吗?

编辑:Apple 就我的 TSI 回复了我,他们说正在调查中,没有解决方法,请等待错误。他们帮不上忙,退了我的 TSI。

编辑 8:在 macOS High Sierra 上出现同样的问题,使用 NSColor 而不是 UIColor。

Apple 仍然没有就我的实际错误报告给我任何反馈。

Apple 回复了我,有新的 persistentStore 选项!

我从苹果收到的短信:

/* 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));

目前还不清楚,但基本上您必须提供 类 的 NSSet,您在打开持久存储时将其用作符合 NSSecureCoding 的可转换属性。

我使用 UIColor 的示例:

NSError *localError;
NSDictionary *options;
if (@available(iOS 11.0, *)) {
    options = @{
                NSMigratePersistentStoresAutomaticallyOption : @YES,
                NSInferMappingModelAutomaticallyOption : @YES,
                NSBinaryStoreSecureDecodingClasses : [NSSet setWithObjects:[UIColor class], nil]
               };

} else {
    // Fallback on earlier versions
    options = @{
                NSMigratePersistentStoresAutomaticallyOption : @YES,
                NSInferMappingModelAutomaticallyOption : @YES,
                };
}
NSPersistentStore *newStore = [self.psc addPersistentStoreWithType:NSBinaryStoreType configuration:@"iOS" URL:psURL options:options error:&localError];

编辑:为使用 NSPersistentStoreDescription 打开核心数据持久存储的新方法添加解决方案。此代码基于当前核心数据模板。

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            NSURL *defaultURL = [NSPersistentContainer defaultDirectoryURL];
            defaultURL = [defaultURL URLByAppendingPathComponent:@"CoreDataTransformableAttribBug.binary"];
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataTransformableAttribBug"];
            NSPersistentStoreDescription *desc = [NSPersistentStoreDescription persistentStoreDescriptionWithURL:defaultURL];

            desc.type = NSBinaryStoreType;
            if (@available(iOS 11.0, *)) {
                [desc setOption:[NSSet setWithObjects:[UIColor class], nil] forKey:NSBinaryStoreSecureDecodingClasses];
            }
            _persistentContainer.persistentStoreDescriptions = @[desc];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                    /*
                     Typical reasons for an error here include:
                     * The parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                    */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                } else {
                    NSLog(@"Description = %@", storeDescription);
                }
            }];
        }
    }

    return _persistentContainer;
}

我还在分支中更新了我的 gitHub 项目

乔治做了所有艰苦的工作。我只将它应用于 Swift。这是我的解决方案。我把它放到我的 NSPersistentDocument 后代中。

override func configurePersistentStoreCoordinator(for url: URL, ofType fileType: String, modelConfiguration configuration: String?, storeOptions: [String : Any]? = nil) throws {
    var options = storeOptions != nil ? storeOptions! : [String:Any]()
    if #available(OSX 10.13, *) {
        options[NSBinaryStoreSecureDecodingClasses] = NSSet(object: NSColor.self)
    }
    options[NSMigratePersistentStoresAutomaticallyOption] = true
    options[NSInferMappingModelAutomaticallyOption] = true
    try super.configurePersistentStoreCoordinator(for: url, ofType: fileType, modelConfiguration: configuration, storeOptions: options)
}

现在我可以再次阅读我的文件了。谢谢乔治!