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;
}
Related
I followed along with Ray Wenderlich's tutorial for saving game data using a singleton and NSCoding (http://www.raywenderlich.com/63235/how-to-save-your-game-data-tutorial-part-1-of-2). Everything they did I've been able to use for NSStrings (changing value, storing, using in my project). I tried to make an NSMutableArray so I could add to and save a list of strings, though, and it didn't work. There is probably a very simple solution to this, so thanks in advance for putting up with a newbie.
My code:
in RWGameData.h (my singleton for using game data):
#property (nonatomic, strong) NSMutableArray *dataArray;
in RWGameData.m, after #implementation:
static NSString* const GameDataArrayofValues = #"dataArray";
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.dataArray forKey:GameDataArrayofValues];
}
-(instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (self) {
_dataArray = [[decoder decodeObjectForKey:GameDataArrayofValues] mutableCopy];
}
return self;
}
(there's also all the other code from that tutorial, like a save method etc.)
in myGame.m
[[RWGameData sharedGameData].dataArray addObject:#"objectString"];
[[RWGameData sharedGameData] save];
Logging the contents of the array right after this shows it as empty. [RWGameData sharedGameData].dataArray accepts the addObject call because I created it as an NSMutableArray, but it seems like it isn't actually going through.
Any ideas?
My guess is _dataArray is nil.
You should add code to your initWithCoder that checks to see if it was able to read array data. If not, _dataArray will be nil. In that case you should initialize an empty mutable array.
Your updated initWithCoder might look like this:
-(instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (self)
{
_dataArray = [[decoder decodeObjectForKey:GameDataArrayofValues] mutableCopy];
if (_dataArray)
_dataArray = [[NSMutableArray alloc] init];
}
return self;
}
Likewise you need to implement a "regular" init method that sets _dataArray to an empty mutable array.
I'm working on a app that can randomize love couples. Just a fun thing, okey!?!? :D
But the problem, or maybe not a problem but a thing that can be much better if I get this thing to be working. In the beginning you need to write in all the names. And thats takes some time... Should I use Core Date? I don't really knows what core data is so I'm not sure. I would love if a god come to me and wrote the full code that can remember an array even if the app and phone shuts down. I have done this in java, is that simpel that it is in java? That would be great!
//Thank, Anton
For Heavy, complex data structures you would want to use core data,
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdTechnologyOverview.html#//apple_ref/doc/uid/TP40009296-SW1
But seeing as you just want to store an array, You should look into NSUserDefaults.
NSUserDefaults will store given data as long as the app is not deleted. You will most likely want to create some kind of custom DataStorage class for this.
#interface DataStorage : NSObject <NSCoding>
#property (nonatomic, strong) NSMutableArray *arrayToStore;
+ (instancetype)sharedInstance;
- (void)save;
#end
Above is the .h file. As you can see, it follows NSCoding protocols. That provides access to methods which allow you to encode data. You will use the save method to write the data to disk.
#import "DataStorage.h"
#implementation DataStorage
#synthesize arrayOfPeople = _arrayToStore;
+ (DataStorage *)sharedInstance
{
static DataStorage *state = nil;
if ( !state )
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"DataStorageKey"];
if (data)
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[DataStorage alloc] init];
}
}
return state;
}
- (id)init{
if (self = [super init]) {
if (!_arrayToStore) {
_arrayToStore = [[NSMutableArray alloc] init];
}
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
if ([decoder decodeObjectForKey:#"DataStorageArrayToStore"]) {
_arrayToStore = [[decoder decodeObjectForKey:#"DataStorageArrayToStore"] mutableCopy];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_arrayToStore forKey:#"DataStorageArrayToStore"];
}
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
#end
Here is the .m file, which pretty much evaluates to see if there is a saved instance of the class, and if not it will create one. [DataStorage sharedInstance]...
when you want to store some data, you will simply make the class available to said file, #import "DataStorage.m and then use
NSString *testData = [NSString stringWithFormat: #"Test Data String"];
[[DataStorage sharedInstance].arrayToStore addObject: testData];
[DataStorage sharedInstance] save];
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"];
}
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.
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