Archiving (NSCoding) when using cocos2d - EXC_BAD_ACCESS - ios

I'm making a game using Cocos2d-iphone 2.1, and have encountered an error with NSCoding unarchiving implementation.
I've used the same pattern in my previous projects (made with UIKit), and everything worked perfetly. While when I'm implementing this pattern on cocos2d, it just doesn't work.
I have recreated the error on a sample project (which can be downloaded here https://www.dropbox.com/sh/30cyfczcxeyf8mr/uDcJiNYMdd ) and here's what I'm doing:
I've created a simple singleton class "GameStore".
Then I've created the "GameParameters" class which is a property of the singleton and conforms to NSCoding (therefore can be archived).
In that class there's the "highscore" property of type int (for example) which I'd like to keep archived as a high score value.
In the GameStore class I initialize the GameParameters instance for the first time, if there's an archived object - I unarchive it.
In the "HelloWorldLayer" (which is a default cocos2d placeholder class) I call NSLog to display the "highscore" value and then set it to a new value
In AppDelegate I call the "save data" method to archive data whenever the home button is pressed.
That's it.
When I start the app for the first time, everything works well, and I save the data by pressing the home button.
And when I start the app again, I run into random (as it seems) EXT_BAD_ACCESS errors... Sometimes when I'm getting the highscore value, sometimes when archiving it again.
Any idea what I might be doing wrong here?
Thanks!
GameStore class
Header file:
#import <Foundation/Foundation.h>
#class GameParameters;
#interface GameStore : NSObject
#property (retain, nonatomic) GameParameters * parameters;
+ (GameStore *) game;
- (void) saveData;
#end
Implementation file:
#import "GameStore.h"
#import "GameParameters.h"
static GameStore * game = nil;
#implementation GameStore
#synthesize parameters;
+ (GameStore *) game
{
if (!game) game = [[super allocWithZone:nil] init];
return game;
}
- (id) init
{
if (game != nil) {
return game;
}
self = [super init];
if (self) {
// Initialization
// Try and load the "GameParameters" from archive
parameters = [NSKeyedUnarchiver unarchiveObjectWithFile:[self archivePath]];
NSLog(#"Highscore: %i",parameters.highscore);
// If there's no archive, initialize the "GameParameters"
if (!parameters) {
parameters = [[GameParameters alloc] init];
parameters.highscore = 12345;
}
}
return self;
}
- (void) saveData
{
[NSKeyedArchiver archiveRootObject:parameters toFile:[self archivePath]];
NSLog(#"Data saved");
}
- (NSString *)archivePath
{
NSArray * documentDirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentDir = [documentDirs objectAtIndex:0];
return [documentDir stringByAppendingPathComponent:#"hs.archive"];
}
#end
GameParameters class
Header file:
#import <Foundation/Foundation.h>
#interface GameParameters : NSObject <NSCoding>
#property (nonatomic, assign) int highscore;
#end
Implementation file:
#import "GameParameters.h"
#implementation GameParameters
#synthesize highscore;
- (id) initWithCoder:(NSCoder *)aDecoder
{
highscore = [aDecoder decodeIntForKey:#"1"];
return self;
}
- (void) encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeInt:highscore forKey:#"1"];
}
#end

Related

RLMResults:allObjects crash (iOS Objective-C)

I have old project written on Objective-C. Need to do migration to Realm.
I created several objects/classes inheritance from RLMObject. When I do fetching objects only with one main object type (ConnectionRealm) - working fine, but if I do add (only add, not include, not use) to project two or more another classes (inheritance from RLMObject), like as FloorRealm class, APP crash on [ConnectionRealm allObjects] without any errors.
Also ConnectionRealm contains RLMArray of FloorRealm. App still crashing.
(Can`t solve and understand this few days.) Thanks.
Connection Model:
#import <Foundation/Foundation.h>
#import <Realm/Realm.h>
#import "FloorRealm.h"
#interface ConnectionRealm : RLMObject
#property int connectionID;
#property NSString *name;
#property NSString *localIPAddress;
#property NSString *localPort;
#property NSString *remoteIPAddress;
#property NSString *remotePort;
#property NSString *userName;
#property NSString *password;
#property NSString *deviceID;
#property RLMArray <FloorRealm *> *floors;
- (instancetype)initWith:(NSString *)name
localIP:(NSString *)localIPAddress
localPort:(NSString *)lPort
remoteIP:(NSString *)remoteIPAddress
remotePort:(NSString *)rPort
userName:(NSString *)userName
password:(NSString *)password
deviceID:(NSString *)deviceID;
#end
#import "ConnectionRealm.h"
#implementation ConnectionRealm
- (instancetype)initWith:(NSString *)name
localIP:(NSString *)localIPAddress
localPort:(NSString *)lPort
remoteIP:(NSString *)remoteIPAddress
remotePort:(NSString *)rPort
userName:(NSString *)userName
password:(NSString *)password
deviceID:(NSString *)deviceID {
if (self = [super init]) {
self.connectionID = [self incrementID];
self.name = name;
self.localIPAddress = localIPAddress;
self.localPort = lPort;
self.remoteIPAddress = remoteIPAddress;
self.remotePort = rPort;
self.userName = userName;
self.password = password;
self.deviceID = deviceID;
}
return self;
}
+ (NSString *)primaryKey { return #"connectionID"; }
- (int)incrementID {
RLMResults *objects = [ConnectionRealm allObjects];
return self.connectionID = [[objects maxOfProperty:#"connectionID"] intValue] + 1;
}
#end
FloorModel:
#import <Realm/Realm.h>
#interface FloorRealm : RLMObject
#property int floorID;
#property NSInteger floorNumber;
#property NSString *floorName;
- (instancetype)initWith:(NSInteger)floorNumber floorName:(NSString *)name;
#end
RLM_ARRAY_TYPE(FloorRealm)
#import "FloorRealm.h"
#implementation FloorRealm
- (instancetype)initWith:(NSInteger)floorNumber floorName:(NSString *)name {
if (self = [super init]) {
self.floorID = [self incrementID];
self.floorNumber = floorNumber;
self.floorName = name;
}
return self;
}
+ (NSString *)primaryKey { return #"floorID"; }
- (int)incrementID {
RLMResults *objects = [FloorRealm allObjects];
return self.floorID = [[objects maxOfProperty:#"floorID"] intValue] + 1;
}
#end
[SOLVED]
RLM_ARRAY_TYPE(FloorRealm) need put on ConnectionRealm in .h after #includes. But in official docs written another.
Also: #property RLMArray <FloorRealm *><FloorRealm> *floors; instead of #property RLMArray <FloorRealm *> *floors;
I created test project with the same models and seed all errors. Strange, but in original project Xcode not showing this errors.

Trouble setting an NSMutableDictionary inside of another NSMutableDictionary

I need to take information submitted by a user, store that information in an NSMutableDictionary, then store that NSMutableDictionary inside another NSMutableDictionary which is then encoded inside another class. For whatever reason, I can't seem to store the first NSMutableDictionary inside of the other.
I had to slim down the code that's in here due to work rules, so sorry if it seems to be missing anything. I only posted the parts that I'm having trouble with.
UserInfo.h:
#import <Foundation/Foundation.h>
#interface MyPlanInfo : NSObject <NSCoding>
#property (nonatomic, strong) NSMutableDictionary *emergencyDictionary;
#end
UserInfo.m:
#import <Foundation/Foundation.h>
#import "MyPlanInfo.h"
static NSString *emergencyDictionaryKey = #"emergencyDictionaryKey";
#implementation MyPlanInfo
#synthesize emergencyDictionary;
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
self.emergencyDictionary = [coder decodeObjectForKey:emergencyDictionaryKey];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.emergencyDictionary forKey:emergencyDictionaryKey];
}
#end
infoView.h
#import <UIKit/UIKit.h>
#import "MyPlanInfo.h"
#interface infoView : UIViewController <NSCoding>
{
NSMutableDictionary *emergencyContactInfo;
NSArray *userInfo;
NSArray *userKeys;
NSMutableArray *tempArray;
}
#property (nonatomic, strong) MyPlanInfo *myPlanInfoObject;
-(void)saveUserInfo;
-(void)loadUserInfo;
#end
infoView.m:
#import "infoView.h"
#interface infoView ()
#end
#implementation infoView
static NSString *userInfoKey = #"userInfoKey";
static NSString *userName;
-(void)viewDidLoad
{
[super viewDidLoad];
if(!self.myPlanInfoObject)
{
self.myPlanInfoObject = [[MyPlanInfo alloc] init];
}
[self loadUserInfo];
}
-(void)addToDictionary
{
emergencyContactInfo = [NSMutableDictionary dictionaryWithObjects:userInfo forKeys:userKeys];
if([userInfo count] != 0 || userInfo == nil)
{
self.myPlanInfoObject.emergencyDictionary = [NSMutableDictionary dictionaryWithObject:emergencyContactInfo forKey:userName];
}
[self saveUserInfo];
}
- (void)saveUserInfo
{
NSData *userInfoData = [NSKeyedArchiver archivedDataWithRootObject:self.myPlanInfoObject];
[[NSUserDefaults standardUserDefaults] setObject:userInfoData forKey:userInfoKey];
}
- (void)loadUserInfo
{
NSData *userInfoData = [[NSUserDefaults standardUserDefaults] objectForKey:userInfoKey];
if(userInfoData)
{
self.myPlanInfoObject = [NSKeyedUnarchiver unarchiveObjectWithData:userInfoData];
}
}
#end
In infoView.m, in the addToDictionary method, userInfo is an array of user inputted information, and userKey's is an array of key's. The emergencyContactInfo NSMutableDictionary works just fine, everything is in it, but when I try to set that as an object in a new NSMutableDictionary, for a key, it doesn't work. Everything is nil.
Anyone have any ideas on how what I'm doing wrong?
Edit: If you down vote, please leave a reason as to why so that I can avoid doing whatever I did wrong in the future.
In the following line you’re creating an instance of MyPlanInfo using plain alloc/init:
self.myPlanInfoObject = [[MyPlanInfo alloc] init];
However, at least in the code provided, you haven’t overridden init in MyPlanInfo, but instead, initWithCoder::
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
self.emergencyDictionary = [coder decodeObjectForKey:emergencyDictionaryKey];
return self;
}
When you use just plain init, the MyPlanInfo’s emergencyDictionary instance variable will be nil. You should likely add something like the following to MyPlanInfo to override init:
- (id) init
{
if ((self = [super init])) {
emergencyDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
That will assure that the newly created MyPlanInfo instance has a proper NSMutableDictionary that can be manipulated from other classes.

How to create an array outside of a function, and be able to add to it in other functions

It seems if I do something like:
NSMutableArray *randomSelection = [[NSMutableArray alloc] init];
Then this needs to be in a function, and I can't modify it later using a different function.
I tried just instantiating it in the .h file,
#interface ViewController:
{
NSMutableArray *Values;
}
But then when I try to append to it during runtime, nothing happens. I try to append to it with this:
int intRSSI = [RSSI intValue];
NSString* myRSSI = [#(intRSSI) stringValue];
[Values addObject:myRSSI];
But the array remains empty when I do this.
How can I fix this?
The recommended way is to create a property;
// ViewController.h
#interface ViewController : UIViewController
{
}
#property (nonatomic, strong) NSMutableArray *values;
#end
Then override the getter for that property, to lazy-initialize it, i.e. the array will be allocated and initialized on first call of the NSMutableArray property's getter:
// ViewController.m
#interface ViewController ()
#end
#implementation ViewController
- (NSMutableArray *)values
{
if (!_values) {
_values = [[NSMutableArray alloc] init];
}
return _values;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//int intRSSI = [RSSI intValue];
//NSString *myRSSI = [#(intRSSI) stringValue];
//[self.values addObject:myRSSI];
// Keep it simple:
[self.values addObject:RSSI];
}
#end

Create MutableArray and add some NSArrays containing Objects

I'm currently learning some objective C but still have problems with the syntax and creating objects.
The Situation: I need a two dimensional "personsArray" which contains many personArrays each containing a NSString *name and a NSNumber *amount (double). Finally I want to calculate some stuff with the array data in another view but I'm far apart from that..
My Plan: Creating a NSMutable Object when the program starts. If I click the Button "Add Person", it creates a personArray with two fixed values (later it should grab those of textAreas). Here is my Code:
Person.h
#import <Foundation/Foundation.h>
#interface Person : NSObject{
NSNumber *amount;
NSString *name;
}
- (void) createPersonArray:(double)theAmount withName:(NSString*) aName;
#end
Person.m
#import "Person.h"
#implementation Person
- (void) createPersonArray:(NSNumber*)theAmount withName:(NSString*) aName{
NSArray* personArray = [NSArray arrayWithObjects:theAmount,aName,nil];
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak,nonatomic) IBOutlet NSMutableArray *personsArray;
- (IBAction)addPerson:(id)sender;
#end
ViewController.m
#import "ViewController.h"
#import "Person.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (IBAction)addPerson:(id)sender {
Person *newPerson;
newPerson = [Person new];
[newPerson createPersonArray:100.00 withName:#"test"];
[_personsArray addObject:newPerson];
}
#end
I know that this wont work, but I don't know how to realize the stuff that I described above. If I build the program, the app starts. if I click the button the debugger jumps to the createPersonArray and outputs "Thread 1: EXC_BAD_ACCESS (code=1,address=0x40590000] and "Unused variable 'personArray'".
I watched many tutorials know but none of them explained this situation.. How must my code be structured to accomplish my goal? Am I on the right path or is it the wrong approach?
greetings
I think if you want save array to MutableArray, you only need create method in ViewController. If you create Person.h, i suggest you should save to MutableArray is Person Object. You can use code below:
Person.h:
#property (nonatomic, strong) NSNumber *amount;
#property (nonatomic, strong) NSString *name;
instead:
NSNumber *amount;
NSString *name;
Person.m:
#implementation Person
#synthesize name;
#synthesize amount;
- (void) createPersonArray:(double)theAmount withName:(NSString*) aName{
amount = [NSNumber numberWithDouble:theAmount];
name = aName;
}
ViewController.h
#property (strong,nonatomic) NSMutableArray *personsArray;
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
_personsArray = [[NSMutableArray alloc]init];
}
- (IBAction)addPerson:(id)sender {
Person *newPerson;
newPerson = [Person new];
[newPerson createPersonArray:100.00 withName:#"test"];
[_personsArray addObject:newPerson];
}
If you don't understand. You can comment below, i will help you.
When you need get contain, you can use:
for (Person *person in _personsArray){
NSLog(#"Amount: %# \n Name: %# \n ",ps.amount,ps.name,);
}
or
for (int i = 0; i< [_personsArray count]; i++){
Person *ps = [_personsArray objectAtIndex:i];
NSLog(#"Amount: %# - Name:%#",ps.amount,ps.name);
}
================================== UPDATE ===========================
double sumAmount = 0;
for (int i = 0; i < [_personsArray count]; i++){
Person *person = [_personsArray objectAtIndex:i];
sumAmount = sumAmount + [person.amount doubleValue];
}
double averageAmount = sumAmount / [_personsArray count];
NSLog(#"%f",averageAmount);
First, personsArray variable is not a outlet. So, remove outlet property and replace weak with strong:
#property (strong,nonatomic) NSMutableArray *personsArray;
Second, need to initialize NSMutableArray before using it, maybe in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
_personsArray = [NSMutableArray new];
}

Can't restore archived data

Ok, I've been over this a million times in the last week and I just am not getting it. (And yes, I've read Apple's docs.)
I am archiving my object and it appears to be archiving correctly (I can see the file written to the file system and if I examine it I can see my data within). However, when I relaunch my app my data is not being restored. Every example I read tells me how easy this is but I'm just not getting it. One unique thing is that my object is a singleton, it's used for passing data between view controllers.
I'd really appreciate some sage advice. Thanks in advance.
Here's my header:
#import <Foundation/Foundation.h>
#interface SharedAppDataObject : NSObject <NSCoding>
{
NSMutableDictionary *apiKeyDictionary;
NSString *skuFieldText;
NSIndexPath *checkmarkIndex;
}
+ (SharedAppDataObject *)sharedStore;
#property (nonatomic, copy) NSString *skuFieldText;
#property (nonatomic, copy) NSIndexPath *checkmarkIndex;
#property (nonatomic, copy) NSMutableDictionary *apiKeyDictionary;
-(void)setValue:(NSString *)apiKey forKey:(NSString *)name;
-(void)setSkuField:(NSString *)s;
-(void)setCheckmarkIndex:(NSIndexPath *)p;
-(NSMutableDictionary *)apiKeyDictionary;
-(BOOL)saveChanges;
#end
Here's my implementation:
#import "SharedAppDataObject.h"
#implementation SharedAppDataObject
#synthesize skuFieldText;
#synthesize checkmarkIndex;
#synthesize apiKeyDictionary;
//create our shared singleton store
+(SharedAppDataObject *)sharedStore {
static SharedAppDataObject *sharedStore = nil;
if (!sharedStore) {
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
if(!sharedStore)
sharedStore = [[super allocWithZone:NULL] init];
}
return sharedStore;
}
-(id) init {
self = [super init];
if (self) {
}
return self;
}
-(void)setValue:(id)apiKey forKey:(NSString *)name {
[apiKeyDictionary setObject:apiKey forKey:name];
}
-(void)setSkuField:(NSString *)s {
skuFieldText = s;
}
-(NSMutableDictionary *)apiKeyDictionary {
return apiKeyDictionary;
}
-(void)setCheckmarkIndex:(NSIndexPath *)p {
checkmarkIndex = p;
}
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:skuFieldText forKey:#"skuFieldText"];
[aCoder encodeObject:checkmarkIndex forKey:#"checkmarkIndex"];
[aCoder encodeObject:apiKeyDictionary forKey:#"apiKeyDictionary"];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self setSkuFieldText:[aDecoder decodeObjectForKey:#"skuFieldText"]];
[self setCheckmarkIndex:[aDecoder decodeObjectForKey:#"checkmarkIndex"]];
[self setApiKeyDictionary:[aDecoder decodeObjectForKey:#"apiKeyDictionary"]];
}
return self;
}
+(NSString *)archivePath {
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"bbyo.archive"];
}
-(BOOL)saveChanges {
return [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
}
#end
Save method from App Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
BOOL success = [[SharedAppDataObject sharedStore] saveChanges];
if (success) {
NSLog(#"Saved all the data");
} else {
NSLog(#"Didn't save any of the data");
}
}
Initialize sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]]; in application:didFinishLaunchingWithOptions:. This method is used to initialize data structures and restore previous app state.
Also, take out static SharedAppDataObject *sharedStore = nil; from sharedStore. If the save file exists, [ShareAppDataObject sharedStore] will always unarchive the file which is not necessary. It can be unarchived once during initialization.
Here's a post that can answer your problem: http://bit.ly/PJO8fM
I cannot give you the answer but some ideas to figure this out. Taking this line:
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
So if the sharedStore is nil, something is wrong - so test for it. If nothing then log the path, and use NSFileManager methods to see if the file is there, its size etc. If you find the file is there and has size, but you cannot unarchive it, that's a problem of course. In that case, add special debug code just after you create the file:
-(BOOL)saveChanges {
BOO ret = [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
id foo = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
// check if foo is not nil, if its the proper class, etc.
}
If when you save the file you can unarchive it just fine, but cannot on restart of the app, then something is wrong with the file. All this info should point the way to a solution.
Another thought - when you encode the data, log it, just to be sure its not nil - but even if so the unarchive should work.

Resources