使用Core Data存储自定义对象

时间:2013-12-12 09:30:51

标签: ios objective-c cocoa-touch core-data

我试图将包含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,因为我无法直接实例化并初始化它。

3 个答案:

答案 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)

使用此initWithCoderencodeWithCode方法。我希望它能为您效劳。它对我来说与你有相同的问题...使用此示例代码

-(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"];
}