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.
encodeWithCoder:is part of theNSCoderprotocol. If you need serialization of any custom object you will need to implementNSCoding