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.
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.
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"];
}
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"];
}
Related
I am trying to archive an NSObject that I send through match data in a game center turn based game.
Below is my code for archiving my object
turnDataObject MyData = [[turnDataObject alloc] init];
data = [NSKeyedArchiver archivedDataWithRootObject:MyData];
This is my code for unarchiving my object
readMyData = [NSKeyedUnarchiver unarchiveObjectWithData:data] ;
However when I run this code I get an error
thread 1 exc bad access code
I think that this might have to do with sending addresses when I archive data. How do I send something that will be readable when I unarchive it?
Edit 1: I get the error on the next line after I unarchive. it says that the adress I am trying to access is null. I remember reading somewhere that I souldn't send adresses of my NSObject but I am not sure how to convert it to something else.
readMyData = [NSKeyedUnarchiver unarchiveObjectWithData:data] ;
NSLog(#"current game happens to be: %#", readMyData.currentGame);
Edit 2: here is my init with coder and encode with coder
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_currentGame = [decoder decodeObjectForKey:currentGameDataKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
//scores data keys
[encoder encodeObject:self.currentGame forKey:currentGameDataKey];
}
Edit 3: _currentGame is in my objects .h file
#property (assign, nonatomic) NSString *currentGame;
I would suggest creating NSKeyedArchiver and NSKeyedUnarchiver objects and using those instead of using the type (which it looks like you're doing.)
I usually program in Swift but here's a shot at example code:
theArchiver NSKeyedArchiver = [[theArchiver alloc] init];
data = [theArchiver archivedDataWithRootObject:MyData];
Then you would do the same thing with the NSKeyedUnarchiver.
Your initWithCoder implementation is wrong:
self = [self init];
That should be:
self = [super init];
You need to add NSCoding protocol to MyData Class, here is the code snippet with supporting NSCoding in order to add Archiving support to NSObjet.
MyData.h
#interface MyData : NSObject <NSCoding>
#property (nonatomic, strong) NSString *currentGame;
#end
MyData.m
//This method is optional, if you need constructor for current game
- (instancetype)initWithCurrentGame:(NSDictionary *)currentGame {
self = [super init];
if (self) {
self.currentGame = currentGame;
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.currentGame forKey:#"currentGame"];
}
-(id)initWithCoder:(NSCoder *)decoder {
self.currentGame = [decoder decodeObjectForKey:#"currentGame"];
return self;
}
How do I convert my NSManagedObject to NSData object?
I'm new to Core Data and the Multipeer Connectivity Framework.
I need to transfer data between 2 devices via the Multipeer Connectivity Framework. I understand that I cannot simply transfer via MPC since it requires an NSData object.
Are there any third-party libraries that provides such function?
I think NSCoding is not such a good idea here. The reason is that the objects will not be the same on two different devices due to their internal managed object IDs as well as a myriad of other possible problems that can occur in unexpected syncing scenarios.
I would strongly recommend to take the trouble and convert your object into a NSDictionary type and then use the standard NSData APIs on the dictionary (or an array of dictionaries).
try like this
You should use NSCoding protocol, then you can encode your object to NSData. Again If you want decode your original object use NSKeyedUnarchiver.
in .h
#interface Testting : NSManagedObject<NSCoding>
and this in .m
NSData *data=[NSKeyedArchiver archivedDataWithRootObject:hereyourObject];
//get your original object
Testting *Obj = [NSKeyedUnarchiver unarchiveObjectWithData:data];
You should use NSCoding protocol. Using NSKeyedAchiever you can encode your object to NSData. Again If you want decode your original object use NSKeyedUnarchiver.
#interface Test : NSManagedObject <NSCoding>
#property (nonatomic, retain) NSString *title;
#end
#implementation Test
#dynamic title;
- (id)initWithCoder:(NSCoder *)coder {
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"Test" inManagedObjectContext:<YourContext>];
self = [super initWithEntity:entity insertIntoManagedObjectContext:nil];
NSArray * attributeNameArray =
[[NSArray alloc] initWithArray:self.entity.attributesByName.allKeys];
for (NSString * attributeName in attributeNameArray) {
[self setValue:[aDecoder decodeObjectForKey:attributeName] forKey:attributeName];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.title forKey:#"title"];
}
#end
//converting to NSData
NSData *archivedObject = [NSKeyedArchiver archivedDataWithRootObject:testObj];
//get your original object
Test *testObj = [NSKeyedUnarchiver unarchiveObjectWithData:archivedObject];
I have this class with an NSMutableArray of custom Cocos2d objects implementing the NSCoding protocol.
#interface PlayerData : NSObject <NSCoding> {
}
#property (readwrite, nonatomic) NSMutableArray *levelsStars; //Where levelsStars is filled with objects conforming to the NSCoding protocol.
The objects are of type LevelData, which inherits from CCNode (a Cocos2d class) and conforms to the NSCoding protocol.
#interface LevelData : CCNode <NSCoding>
Here is the implementation of the protocol in PlayerData. In order to encode and decode the NSMutableArray of custom Cocos2d objects, which conform to NSCoding objects:
-(void) encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:[NSKeyedArchiver archivedDataWithRootObject:levelsStars] forKey:kLevelsStars];
}
-(id) initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
//Reading arrays:
NSData * dataRepresentingLevelStars = [aDecoder decodeObjectForKey:kLevelsStars];
if (dataRepresentingLevelStars!=nil) {
NSArray * oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingLevelStars];
if (dataRepresentingLevelStars!=nil) {
levelsStars = [NSMutableArray arrayWithArray:oldSavedArray];
}
else
{
levelsStars = [[NSMutableArray array] initWithCapacity:10];
}
}
}
return self;
}
Is this approach correct? I based it on this question / answer.
EDIT: I thought I will add some more details on my use case
My users can choose among different characters, each characters corresponds to a PlayerData object which I plan to store in a different file appending the user's game center id (id-character1.archive, id-character2.archive etc..).
I do save progress of the characters in the object (e.g. score, life) including the NSMutableArray custom array of Cocos2d-objects (in my real case I do have 5 different arrays containing 20/30 objects each).
In your main init method for the PlayerData object you want to do the unarchive:
- (id)init {
self = [super init];
if (self) {
self.levelsStars = [NSKeyedUnarchiver unarchiveObjectWithFile:pathToArchive];
if (self.levelsStars == nil) self.levelsStars = [NSMutableArray array];
}
}
This will then call initWithCoder: on each of your objects that was in the array. And pull them back into a mutable array.
You will want to have some method for saving your data though, again on your PlayerData object:
- (void)applicationDidEnterBackground:(NSNotification *)notification {
[NSKeyedArchiver archiveRootObject:self.levelsStars toFile:pathToArchive];
}
This will call encodeWithCoder: on each of the objects in the array and write them out to a file.
I am making a NSObjectClass that has a method in it that returns self.
This is what it looks like roughtly
storageclass.h
// storageclass vars go here
- (storageclass)assignData:(NSDictionary *)dictionary;
storageclass.m
//#synthesise everything
- (storageclass)assignData:(NSDictionary *)dictionary {
//assign values from dictionary to correct var types (i.e. NSString, Int, BOOL)
//example
Side = [dictionary valueForKey:#"Side"];
return self;
}
Then what I want to do is use this class by passing a NSDictionary var through its method to return a object of type storageclass that I can then use to access the vars using dot notation.
this is how I am trying to access this class at the moment
accessorViewController.h
storageclass *store;
#property (strong, nonatomic) storageclass *store;
accessorViewController.m
#synthesize store;
- (void)getstoreready {
[store assignData:someDictionary];
nslog(#"%#", store);
}
this NSLog returns nothing and in the debugger all of stores class vars are empty showing nothing has been assigned. I am 100% positive the dictionary vars being used in the assignData method have the correct valueForKey values.
I think it has something to do with how I am using it here [store assignData:someDictionary]; how do i catch the turned data so I can use it?
any help would be appreciated.
The store object is never initialized so it will be nil thats obvious isn't it. Initialize the store object first, then call its instance methods onto it. And by doing that, you'll have a storageclass object which is properly assigned with some dictionary already.
And if you want to have a storageclass object like your code shows, you should make your (storageclass)assignData:(NSDictionary *)dictionary method a class method instead of an instance method by putting a + sign
+(storageclass*)assignData:(NSDictionary *)dictionary;
Then properly initialize it and assign the data (dictionary to variables) accordingly and return it to the caller. For example :-
in .m file
+(storageclass*)assignData:(NSDictionary *)dictionary{
storageclass *test = [[storageclass alloc] init];
if (test) {
test.someDict = dictionary;
}
return test;
}
Then use this class method in your view controller as
- (void)getstoreready {
store = [storageClass assignData:someDictionary];
nslog(#"%#", store);
}
Also Do follow the naming convention for classes and instances. A class's name must start with a capital letter only and the opposite for any class instances.
In User.h
#interface User : NSObject
#property (nonatomic, copy) NSString *name;
- (id)initWithDictionary:(NSDictionary *)dictionary;
+ (NSArray *)usersFromArray:(NSArray *)array;
#end
In User.m
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
if (dictionary)
{
self.name = dictionary[#"kUserName"];
}
}
return self;
}
+ (NSArray *)usersFromArray:(NSArray *)array
{
NSMutableArray *users = [NSMutableArray array];
for (NSDictionary *dict in array) {
User *user = [[User alloc]initWithDictionary:dict];
[users addObject:user];
}
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES];
return [users sortedArrayUsingDescriptors:#[descriptor]];
}
In ViewController.m
import "User.h"
self.currentArray = [User usersFromArray:array];
In my application i have an custom NSObject, which contains 2 mutable Arrays.
I need to save this custom NSOBject into a core data entity, but i have no real idea how i can accomplish that...
After some searching, i found out, that the best way would be to convert the nsobject to nsdata and save it in an transformable field of the entity... but i m not sure how to do that.
can someone help me?
heres to code for my custom object:
MeasureData.h
#interface MeasureData : NSObject{
}
#property (nonatomic, strong) NSMutableArray *questionsData;
#property (nonatomic, strong) NSMutableArray *answersData;
- (id) init;
#end
MeasureData.m
#import "MeasureData.h"
#implementation MeasureData
#synthesize questionsData;
#synthesize answersData;
#pragma mark -
#pragma mark int
- (id)init
{
self = [super init];
// Initalize questions array (width data from plist)
questionsData = self.makeQuestionsArray;
// NSLog(#"loaded questions array: %#",questionsData); // debug
// Initalize answers array
answersData = self.makeAnswersArray;
// NSLog(#"loaded answers array: %#",answersData); // debug
return self;
}
-(NSMutableArray *)makeQuestionsArray
{
// Initalize questions array (width data from plist)
NSString *path = [[NSBundle mainBundle] pathForResource:
#"questions.list" ofType:#"plist"];
NSMutableArray *questions = [NSMutableArray arrayWithContentsOfFile:path];
/*
[questionsData insertObject:(NSString *)string atIndex:0];
*/
return questions;
}
-(NSMutableArray *)makeAnswersArray
{
// Initalize answers array
NSMutableArray *answers = [NSMutableArray arrayWithCapacity:0];
return answers;
}
-(id)initWithCoder:(NSCoder *)decoder {
if ((self=[super init])) {
questionsData = [decoder decodeObjectForKey:#"questionsData"];
answersData = [decoder decodeObjectForKey:#"answersData"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:questionsData forKey:#"questionsData"];
[encoder encodeObject:questionsData forKey:#"questionsData"];
}
#end
According to the first comment, i implemented the encoder/coder functions for my custom class. And tried to archive and encode it (i m new to ios, so it could be completly wrong) - but it dont work... can someone tell me whats wrong?
heres the encoding (which dont work XD):
NSMutableData *dataToSave = (NSMutableData *)self.measureData;
NSKeyedArchiver *archiverForData = [[NSKeyedArchiver alloc] initForWritingWithMutableData:dataToSave];
[archiverForData encodeObject:dataToSave forKey:#"dataToSave"];
[archiverForData finishEncoding];
//
//theMeasure is the CoreData Entity
theMeasure.result = dataToSave;
In outline:
create a NSMutableData
create a NSKeyedArchiver with initForWritingWithMutableData over your data
serialize your arrays / objects / whatever you need (that implements NSCoding) with encode... methods of NSCoder
create a managed object with a BLOB (binary data) type field
write your encoded data from the mutable data to this field of the managed object.
In my answer to this question you can find some useful links: NSCoding VS Core data