CoreData+CloudKit集成报错:自定义值转换器问题的原因与解决方法
我使用CoreData+CloudKit存储数据,现有AEntity和BEntity两个实体:
AEntity的content属性为Transformable类型,用于存储NSAttributedString,已在Xcode中指定转换器为AttributedStringValueTransformer,代码如下:
@implementation AttributedStringValueTransformer + (Class)transformedValueClass { return [NSAttributedString class]; } + (BOOL)allowsReverseTransformation { return YES; } - (NSData *)transformedValue:(NSAttributedString *)value { if (!value) { return nil; } if ([value isKindOfClass:[NSData class]]) { return (NSData *)value; } NSData *stringAsData = [NSKeyedArchiver archivedDataWithRootObject:value]; return stringAsData; } - (NSAttributedString *)reverseTransformedValue:(NSData *)value { NSAttributedString *attributedString = [NSKeyedUnarchiver unarchiveObjectWithData:value]; return attributedString; } @end
BEntity的images属性为Transformable类型,用于存储图片数组(NSArray),已在Xcode中指定转换器为ArrayValueTransformer,代码如下:
@implementation ArrayValueTransformer + (Class)transformedValueClass { return [NSArray class]; } + (BOOL)allowsReverseTransformation { return YES; } - (NSData *)transformedValue:(NSAttributedString *)value { if (!value) { return nil; } if ([value isKindOfClass:[NSData class]]) { return (NSData *)value; } return [NSKeyedArchiver archivedDataWithRootObject:value];; } - (NSAttributedString *)reverseTransformedValue:(NSData *)value { return [NSKeyedUnarchiver unarchiveObjectWithData:value]; } @end
初始化NSPersistentCloudKitContainer的代码如下:
+ (instancetype)containerWithName:(NSString *)name { NSPersistentCloudKitContainer *persistentContainer = [[NSPersistentCloudKitContainer alloc] initWithName:name]; persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES; NSPersistentStoreDescription *description = [persistentContainer.persistentStoreDescriptions firstObject]; [description setOption:@(YES) forKey:NSPersistentHistoryTrackingKey]; [description setOption:@(YES) forKey:NSPersistentStoreRemoteChangeNotificationPostOptionKey]; description.shouldInferMappingModelAutomatically = YES; description.shouldMigrateStoreAutomatically = YES; persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; [persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) { if (error != nil) { NSLog(@"Unresolved error %@, %@", error, error.userInfo); // abort(); } }]; return persistentContainer; }
运行时在loadPersistentStoresWithCompletionHandler的回调中出现以下错误:
Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=CloudKit integration requires that the value transformers for transformable attributes are available via +[NSValueTransformer valueTransformerForName:], return instances of NSData, and allow reverse transformation: AEntity: content - Claims to return instances of NSAttributedString BEntity: images - Claims to return instances of NSArray}
错误原因
CloudKit对CoreData的Transformable属性转换器有三个硬性要求,当前代码全部违反:
- 转换器未全局注册,无法通过
+[NSValueTransformer valueTransformerForName:]被CoreData和CloudKit访问到 - 转换器的
transformedValueClass返回的是原始数据类型(NSAttributedString/NSArray),但CloudKit要求转换后必须是NSData类型,因为它只支持二进制数据存储 ArrayValueTransformer存在类型错误:transformedValue:的参数类型错误写为NSAttributedString,reverseTransformedValue:的返回值也错误写为NSAttributedString,和实际处理的NSArray不匹配
解决步骤
1. 修正转换器的transformedValueClass方法
所有用于CloudKit的Transformable属性转换器,transformedValueClass必须返回NSData.class,因为这是最终存储到CloudKit的类型。同时修正ArrayValueTransformer的类型错误,并替换为安全的编码解码API。
修正后的AttributedStringValueTransformer:
@implementation AttributedStringValueTransformer + (Class)transformedValueClass { return [NSData class]; // 改为返回NSData类型 } + (BOOL)allowsReverseTransformation { return YES; } - (NSData *)transformedValue:(id)value { if (!value) { return nil; } if ([value isKindOfClass:[NSData class]]) { return (NSData *)value; } if ([value isKindOfClass:[NSAttributedString class]]) { // 使用安全编码API,支持Secure Coding return [NSKeyedArchiver archivedDataWithRootObject:value requiringSecureCoding:YES error:nil]; } return nil; } - (NSAttributedString *)reverseTransformedValue:(NSData *)value { if (!value) { return nil; } // 使用安全解码API,指定目标类型 return [NSKeyedUnarchiver unarchivedObjectOfClass:[NSAttributedString class] fromData:value error:nil]; } @end
修正后的ArrayValueTransformer:
@implementation ArrayValueTransformer + (Class)transformedValueClass { return [NSData class]; // 改为返回NSData类型 } + (BOOL)allowsReverseTransformation { return YES; } - (NSData *)transformedValue:(id)value { if (!value) { return nil; } if ([value isKindOfClass:[NSData class]]) { return (NSData *)value; } if ([value isKindOfClass:[NSArray class]]) { // 注意:数组中的元素(如UIImage)需要支持NSCoding,或提前转为NSData存储 return [NSKeyedArchiver archivedDataWithRootObject:value requiringSecureCoding:YES error:nil]; } return nil; } - (NSArray *)reverseTransformedValue:(NSData *)value { if (!value) { return nil; } return [NSKeyedUnarchiver unarchivedObjectOfClass:[NSArray class] fromData:value error:nil]; } @end
2. 全局注册转换器
在App启动阶段(比如AppDelegate的application:didFinishLaunchingWithOptions:方法,或SwiftUI的App结构体初始化代码中),注册这两个转换器,名称要和Xcode实体属性中设置的Transformer名称完全一致:
// 注册自定义转换器 [NSValueTransformer setValueTransformer:[[AttributedStringValueTransformer alloc] init] forName:@"AttributedStringValueTransformer"]; [NSValueTransformer setValueTransformer:[[ArrayValueTransformer alloc] init] forName:@"ArrayValueTransformer"];
3. (可选)确保数据类型支持安全编码
如果数组中的图片是UIImage,需要确认它支持NSCoding(默认支持),或者提前将图片转为NSData再存入数组,避免编码失败。
内容的提问来源于stack exchange,提问作者Lix

