1

I'm trying to store a NSMutableArray consisting of VOs (NameVO) with Core Data but getting the following exception thrown:

'NSInvalidArgumentException', reason: '-[NameVO encodeWithCoder:]: unrecognized selector sent to instance 0x1096400a0'

My NameVO is just a simple value object, extending NSObject and contains two fields, a string and a NSMutableArray that itself contains strings. It also contains a compare function.

I'm trying to prepare this to be stored as a CD transformable attribute type with ('names' is my NameVO array):

NSData *tempNamesData = [NSKeyedArchiver archivedDataWithRootObject:names];

My question is: what do I need to do to make NameVO be accepted by NSKeyedArchiver to convert it successfully to a NSData?

I don't want NameVO to extend NSManagedObject because then I cannot instantiate and init it directly.

2
  • The encodeWithCoder: is part of the NSCoder protocol. If you need serialization of any custom object you will need to implement NSCoding Commented Dec 12, 2013 at 9:37
  • Thanks! Yes got it all working now! Commented Dec 12, 2013 at 13:22

3 Answers 3

4

Now that we're in 2017, it's best to use the safer NSSecureCoding protocol instead of the older, less safe NSCoding protocol. The implementation changes are minimal:

1) ensure that your class declares its conformation to NSSecureCoding

@interface MyClass : NSObject<NSSecureCoding>

@property (nonatomic, strong) NSNumber *numberProperty;
@property (nonatomic, strong) NSString *stringProperty;

@end

2) NSSecureCoding protocol houses the same two instance methods methods in the NSCoding protocol, plus an additional class method, +supportsSecureCoding. You'll need to add that method, as well as slightly modify your -initWithCoder: method.

@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

Notice that NSCoder's -decodeObjectOfClass:withKey: requires you to specify the class that you're expecting to receive. This is a much safer way to do things.

Then, to store this decodable object in CoreData, simply create a Managed object that contains an NSData attribute and some identifying information (a string, a date, an id, or a number or something)

@interface MyClassMO : NSManagedObject

@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, strong) NSData *data;

@end

@implementation MyClassMO

@dynamic identifier;
@dynamic data;

@end

In practice, it would look something like this:

- (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;

}

This solution is obviously a bit of an oversimplification, how and when you store stuff in the db is up to your needs, but the main idea is that, you'll need to make sure your custom class conforms to NSSecureCoding, and that you'll need to make a separate Managed Object class to store and retrieve your data.

Sign up to request clarification or add additional context in comments.

Comments

3

As your exception says you need to implement NSCoding protocol to your class and you have to override two methods:

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

It should sorted your issue.

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

4 Comments

Ok thanks but how would the implementation for these two methods look?
Thanks for the example but one problem: there is no formId in my class.
FormId is an example you have to use your properties/variables you have in your class. For example if you have in your .h file @property(copy)NSString *var1; replace formId with var1. Remember add decodeObjectForKey and encodeObject for all of your property you want to archive.
Starts to make sense. But now I'm getting a "Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSMutableArray count]: method sent to an uninitialized mutable array object'".
1

Use this initWithCoder and encodeWithCode method.I hope it will work for you. it works for me in same issue as u have...Use this sample code

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.