我试图将包含VO(NameVO)和核心数据的NSMutableArray存储起来,但却引发了以下异常:
'NSInvalidArgumentException', reason: '-[NameVO encodeWithCoder:]: unrecognized selector sent to instance 0x1096400a0'
我的NameVO只是一个简单的值对象,扩展了NSObject并包含两个字段,一个字符串和一个本身包含字符串的NSMutableArray。它还包含一个比较函数。
我试图准备将其存储为CD可转换的属性类型('名称'是我的NameVO数组):
NSData *tempNamesData = [NSKeyedArchiver archivedDataWithRootObject:names];
我的问题是:我需要做些什么才能让NSKeyedArchiver接受NameVO将其成功转换为NSData?
我不希望NameVO扩展NSManagedObject,因为我无法直接实例化并初始化它。
答案 0 :(得分:4)
现在我们在2017年,最好使用更安全的NSSecureCoding协议,而不是旧的,安全性较低的NSCoding协议。实施变化很小:
1)确保您的类声明其符合NSSecureCoding
@interface MyClass : NSObject<NSSecureCoding>
@property (nonatomic, strong) NSNumber *numberProperty;
@property (nonatomic, strong) NSString *stringProperty;
@end
2)NSSecureCoding协议在NSCoding协议中包含相同的两个实例方法方法,另外还有一个类方法+supportsSecureCoding
。您需要添加该方法,并稍微修改您的-initWithCoder:方法。
@implementation MyClass
// New Method for NSSecureCoding. Return YES.
+ (BOOL)supportsSecureCoding {
return YES;
}
// Your Encode Method Can Stay The Same, though I would use NSStringFromSelector whenever possible to get the keys to ensure you're always getting what you're looking for.
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.numberProperty forKey:NSStringFromSelector(@selector(numberProperty))];
[aCoder encodeObject:self.stringProperty forKey:NSStringFromSelector(@selector(stringProperty))];
}
// Slightly updated decode option
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.numberProperty = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(numberProperty))];
self.stringProperty = [aDecoder decodeObjectOfClass:[NSString class] forKey:NSStringFromSelector(@selector(stringProperty))];
}
}
@end
请注意,NSCoder的-decodeObjectOfClass:withKey:
要求您指定您希望收到的课程。这是一种更安全的做事方式。
然后,要将此可解码对象存储在CoreData中,只需创建一个包含NSData属性和一些标识信息(字符串,日期,ID或数字等)的Managed对象。
@interface MyClassMO : NSManagedObject
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, strong) NSData *data;
@end
@implementation MyClassMO
@dynamic identifier;
@dynamic data;
@end
在实践中,它看起来像这样:
- (void)storeObject:(MyClass *)object withIdentifier:(NSString *)identifier {
NSData *objectData = [NSKeyedArchived archivedDataWithRootObject:object];
NSManagedObjectContext *moc = ... // retrieve your context
// this implementation relies on new NSManagedObject initializers in the iOS 10 SDK, but you can create it any way you typically create managed objects
MyClassMO *managedObject = [[MyClassMO alloc] initWithContext:moc];
managedObject.data = objectData;
managedObject.identifier = identifier;
NSError *error;
[moc save:&error];
}
- (MyClass *)retrieveObjectWithIdentifier(NSString *)identifier {
NSManagedObject *moc = ... // retrieve your context
// This also relies on iOS 10 SDK's new factory methods available on NSManagedObject. You can create your request any way you typically do;
NSFetchRequest *request = [MyClassMO fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"identifier = %@", identifier];
request.predicate = predicate;
NSError *error;
NSArray<MyClassMO *> *results = [moc executeFetchRequest:request withError:&error];
// if you're only storing one object per identifier, this array should only be 1 object long. if not, you'll need to decide which object you're looking for. you also might want to implement an overwrite policy or a delete before store type thing.
MyClassMO *managedObject = results.firstObject;
NSData *objectData = managedObject.data;
MyClass *object = [NSKeyedUnarchiver unarchiveObject:objectData];
return object;
}
这个解决方案显然有点过于简单化了,在数据库中存储内容的方式和时间可以满足您的需求,但主要的想法是,您需要确保自定义类符合NSSecureCoding,并且您需要创建一个单独的Managed Object类来存储和检索数据。
答案 1 :(得分:3)
正如你的例外所说,你需要为你的类实现NSCoding
协议,你必须覆盖两个方法:
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
它应该排序你的问题。
// EXTENDED
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_formId = [aDecoder decodeObjectForKey:@"FormID"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.formId forKey:@"FormID"];
}
答案 2 :(得分:1)
使用此initWithCoder
和encodeWithCode
方法。我希望它能为您效劳。它对我来说与你有相同的问题...使用此示例代码
-(id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super init]){
storePlaylist=[aDecoder decodeObjectForKey:@"storePlaylist"];
playlistName=[aDecoder decodeObjectForKey:@"playlistName"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:storePlaylist forKey:@"storePlaylist"];
[encoder encodeObject:playlistName forKey:@"playlistName"];
}