iOS NSKeyedUnarchiver calling singleton getter - objective-c

I have a singleton class that will initialize it's data from a web service then save itself to a file (using NSCoding and NSKeyedUnarchiver) so I can just initialize this file instead of pulling all the data from the web again. Because it's a singleton instance that is being saved, I want to be able to call the singleton getter like you normally would, check to see if there's an archived copy and if not, pull the data down and archive it. Archiving is working fine, but when I try to call [[NSKeyedUnarchiver unarchiveObjectWithFile: filePath] retain] it calls the sharedInstance getter before sharedInstance is initialized. This causes init to be called and the app then downloads all the data again, just to be subsequently overwritten by the unarchiver.
Am I doing something wrong with my setup, or is there another way of Serializing this data?
Here's some (simplified) code:
#implementation Helmets
#synthesize helmetList, helmetListVersion;
//Class Fields
static Helmets *sharedInstance = nil;
// Get the shared instance and create it if necessary.
+ (Helmets *)sharedInstance {
//if(sharedInstance == nil){
//[Helmets unarchive]; //This does not work! Calls sharedInstance() again (recursion)
if(sharedInstance == nil){
sharedInstance = [[super allocWithZone:NULL] init]; //Pull from web service
[Helmets archive]; //Save instance
//}
//}
return sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
helmetList = [[NSMutableArray alloc]init];
//Get our data from the web service and save it (excluded)
}
}
return self;
}
//This works!
+(void)archive{
if([NSKeyedArchiver archiveRootObject:sharedInstance toFile:[Helmets getFilePath]]){
NSLog(#"Archiving Successful");
}
else{
NSLog(#"Archiving Failed");
}
}
//This works, but calls getInstance causing data to be downloaded anyways!
+(void)unarchive{
// Check if the file already exists
NSFileManager *filemgr = [NSFileManager defaultManager];
NSString *filePath = [Helmets getFilePath];
if ([filemgr fileExistsAtPath: filePath])
{
sharedInstance = [[NSKeyedUnarchiver unarchiveObjectWithFile: filePath] retain];
}
[filemgr release];
}
Instance is initialized like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
...
[Helmets unarchive]; //This calls sharedInstance() too soon!
[Helmets sharedInstance];
}
The class implements NSCoding in the .h and overrides initWithCoder and encodeWithCoder (Archiving is working).
Thanks in advance!

In the end you need a private method to set your shared instance in addition to the one you have, and you need a different init, again private to the implementation.
- (id)initAndFetch:(BOOL)fetch
{
if((self = [super init])) {
...
if(fetch) { do the web fetch };
...
}
}
In the +sharedInstance method, you will pass YES.
Then your decode will look like:
- (id)initWithCoder:(NSCoder *)decoder
{
if((self = [self initAndFetch:NO])) {
title = [decoder decodeObjectForKey:#"title"];
...
sharedInstance = self;
}
return self;
}

I have got to make it work the following manner.
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (!self) {
return nil;
}
UserInfo *user =[UserInfo sharedInstance];
return user;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
UserInfo *user =[UserInfo sharedInstance];
[aCoder encodeObject: user.phoneNumber forKey:#"phoneNumber"];
}
-(void)save
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:[UserInfo sharedInstance]] forKey:#"USER_DATA"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
-(UserInfo*)currentUser
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"USER_DATA"];
UserInfo *savedUser =[NSKeyedUnarchiver unarchiveObjectWithData:data];
UserInfo *user =[UserInfo sharedInstance];
return user;
}

Related

EXC_BAD_ACCESS for an object created inside a Block

I have always been nervous when it comes to blocks and GCD because my mind tells me that it looks very complex!
I am getting a crash inside a block which ideally looks alright to me:
#pragma mark -
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
__weak VTVehicleServiceNetworkManager *weakSelf = self;
TaskBlock fetchOrdersListTaskBlock = ^()
{
__block __strong HLOrdersDataProvider *ordersDataProvider = nil;
NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:[^{
ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
} copy]];
[weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
};
[self fetchDataWithTaskBlock:[fetchOrdersListTaskBlock copy]
errorBlock:^(NSError *error) {
errorBlock(error);
}];
}
I was able to trace out the zombie object but I am not able to figure out why is this object turning out to be a zombie. Here is the snapshot from profile:
I have gone through the following guides (1, 2) to see if I can find out what I am doing wrong but I was no where near to find out what is going wrong.
Any help and reference text to what I am doing wrong will help.
Edit:
I have tried what #Jerimy has suggested and in fact my code which I have posted earlier was exactly the same as required: Declaring and initializing ordersDataProvider inside the block operation itself. But since it was crashing at the same point I tried to declare it outside the block just to see if it addresses the crash.
Below is the new code I tested:
#pragma mark -
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
__weak VTVehicleServiceNetworkManager *weakSelf = self;
completionBlock = [completionBlock copy];
errorBlock = [errorBlock copy];
TaskBlock fetchOrdersListTaskBlock = ^()
{
NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:^{
HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
}];
[weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
};
[self fetchDataWithTaskBlock:[fetchOrdersListTaskBlock copy]
errorBlock:^(NSError *error) {
errorBlock(error);
}];
}
The crash from Profile:
There is not much from the stack trace as well, SDMHTTPRequest is a library and am very sure there is nothing wrong there, and the HLOrdersDataProvider is the zombie object which I was able to trace out in Instruments app:
EDIT 2
Adding the interface and implementation of HLOrdersDataProvider for more details:
#interface HLOrdersDataProvider : HLDataProvider
-(void)performFetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock;
#end
#implementation HLOrdersDataProvider
-(void)performFetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
// Using SDMConnectivity
NSString *queryString = [infoDict valueForKey:#"$filter"];
NSString *appendStringForEndpoint = [kRowsetsKeyword stringByAppendingFormat:#"?%#", queryString];
[self fetchDataFromServerWithEndPointAppendString:appendStringForEndpoint
completionBlock:completionBlock
errorBlock:errorBlock];
}
#pragma mark - Service Agent related
-(NSString*)collectionName
{
return [NSString stringWithString:kRowsetsKeyword];
}
-(void)requestFinished:(SDMHttpRequest *)request
{
NSError *error = nil;
// Let's parse the response and send the results back to the caller
NSString *collectionName = [self collectionName];
NSData *responseData = [request responseData];
NSArray *entitiesArray = [self parseODataEntriesWithData:responseData
withCollectionName:collectionName
error:&error];
if (error)
[self triggerFailureBlockWithArgument:error];
else
[self triggerCompletionBlockWithArgument:entitiesArray];
}
#end
Also, HLOrdersDataProvider is inherited from HLDataProvider so below is the interface and implementation of this class too:
#import <Foundation/Foundation.h>
//#import "SDMHttpRequestDelegate.h"
#import "SDMRequestBuilder.h"
#import "SDMHttpRequest.h"
#import "SDMParser.h"
#import "HLConstant.h"
#import "HLConnectionData.h"
#interface HLDataProvider : NSObject <SDMHttpRequestDelegate>
#property (copy, atomic) CompletionBlock completionBlock;
#property (copy , atomic) ErrorBlock errorBlock;
#property (copy, atomic) CompletionBlockWithDataFetchStatus completionBlockWithDataFetchStatus;
+ (id)sharedInstance;
- (NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName;
- (NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName error:(NSError**)outError;
- (NSMutableArray*)parseJSONEntriesWithData:(NSData*)data;
-(NSArray*)fetchEntriesFromDatabaseWithEntityName:(NSString*)entityName relationshipObjects:(NSMutableArray*)relationships;
-(void)updateDatabaseWithEntries:(NSMutableArray*)scanEntries;
-(void)updateDatabaseWithJSONEntries:(NSMutableArray*)scanEntries;
-(id)parsedOdataResultFromEntries:(NSMutableArray*)entries;
-(void)fetchDataFromServerWithEndPointAppendString:(NSString*)appendStr completionBlock:(CompletionBlock)inCompletionBlock errorBlock:(ErrorBlock)inErrorBlock;
-(NSString*)collectionName;
-(void)triggerCompletionBlockWithArgument:(id)inArg;
-(void)triggerFailureBlockWithArgument:(NSError*)inArg;
#end
#implementation HLDataProvider
+ (id)sharedInstance
{
//Subclassess will override this method
return nil;
}
-(NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName
{
return [self parseODataEntriesWithData:data
withCollectionName:collectionName
error:NULL];
}
-(NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName error:(NSError**)outError
{
NSMutableArray *entriesArray = nil;
#try {
entriesArray = sdmParseODataEntriesXML(data,
[[[HLConnectionData metaDataDocument] getCollectionByName:collectionName] getEntitySchema],
[HLConnectionData serviceDocument]);
}
#catch (NSException *exception) {
NSLog(#"Got exception: %#", exception);
if (outError)
{
*outError = [NSError errorWithDomain:#"Vehicle Service"
code:-1001
userInfo:[NSDictionary dictionaryWithObject:exception forKey:NSLocalizedDescriptionKey]];
}
}
#finally {
}
return entriesArray;
}
- (NSMutableArray*)parseJSONEntriesWithData:(NSData*)data
{
NSError *error = nil;
id object = [NSJSONSerialization
JSONObjectWithData:data
options:0
error:&error];
NSMutableArray *resultArray = nil;
if(error) { /* JSON was malformed, act appropriately here */ }
if([object isKindOfClass:[NSDictionary class]])
{
resultArray = [NSMutableArray arrayWithObject:object];
}
else if ([object isKindOfClass:[NSArray class]])
{
resultArray = [NSMutableArray arrayWithArray:object];
}
return resultArray;
}
#pragma mark -
#pragma mark - Data Fetch - Server - SDMConnectivity
-(void)fetchDataFromServerWithEndPointAppendString:(NSString*)appendStr completionBlock:(CompletionBlock)inCompletionBlock errorBlock:(ErrorBlock)inErrorBlock;
{
self.errorBlock = inErrorBlock;
self.completionBlock = inCompletionBlock;
id<SDMRequesting> request = nil;
//NSString *clientStr = #"&sap-client=320&sap-language=EN";
NSString *urlStr =[NSString stringWithFormat:#"%#/%#",[HLConnectionData applicationEndPoint], appendStr];
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[SDMRequestBuilder setRequestType:HTTPRequestType];
request=[SDMRequestBuilder requestWithURL:[NSURL URLWithString:urlStr]];
[request setUsername:kUserName];
/*Set Password in SDMRequesting object*/
[request setPassword:kPassword];
[request setRequestMethod:#"GET"];
[request setTimeOutSeconds:kTimeoutInterval];
/*set the Delegate. This class must adhere to SDMHttpRequestDelegate to get the callback*/
[request setDelegate:self];
/*Call startAsynchronous API to request object to retreive Data asynchrnously in the call backs */
[request startSynchronous];
}
-(void)updateDatabaseWithEntries:(NSMutableArray*)scanEntries
{
//Subclasses will override this
}
-(void)updateDatabaseWithJSONEntries:(NSMutableArray*)scanEntries
{
//Subclasses will override this
}
-(id)parsedOdataResultFromEntries:(NSMutableArray*)entries
{
//Subclasses will override this
return nil;
}
-(void)deleteExistingEntriesFromCoredata
{
//Subclasses will override this
}
-(NSArray*)fetchEntriesFromDatabaseWithEntityName:(NSString*)entityName relationshipObjects:(NSMutableArray*)array
{
//Subclasses will overide this method
return nil;
}
#pragma mark - SDMHTTPRequestDelegate methods
- (void)requestStarted:(SDMHttpRequest*) request
{
}
- (void)requestFinished:(SDMHttpRequest*) request
{
// For service doc and metadata we instantiate HLDataProvider, so we send this raw SDMHTTPRequest object as-is. For other service agents like HLOrdersDataProvider we send the parsed information, check the subclass' implementation of -requestFinished: method
[self triggerCompletionBlockWithArgument:request];
}
-(void)triggerCompletionBlockWithArgument:(id)inArg
{
self.completionBlock(inArg);
}
- (void)requestFailed:(SDMHttpRequest*) request
{
[self triggerFailureBlockWithArgument:request.error];
}
-(void)triggerFailureBlockWithArgument:(NSError*)inArg
{
self.errorBlock(inArg);
}
- (void)requestRedirected:(SDMHttpRequest*) request
{
}
#pragma mark - Service Agent related
-(NSString*)collectionName
{
// Should be overridden by the subclasses
return nil;
}
The referenced code is very convoluted (you're doing things in a very complicated way).
First off, you should not be creating the copy of your blocks in the caller. Make the copy in the callee if necessary (ie: to copy it to heap instead of using the stack-allocated block if you are going to call it after the stack has been popped). In almost all APIs using blocks, it is not the caller's responsibility to ensure that a block is on heap.
There is no reason for your ordersDataProvider variable to be declared in the scope of fetchOrdersListTaskBlock because it is only ever used inside of fetchOrdersOperation's block.
I don't immediately see the cause of your crash, but I suspect that simplifying your code will help reveal the problem. Perhaps the issue is in HLOrdersDataProvider's initializer.
Try something like:
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
completionBlock = [completionBlock copy];
errorBlock = [errorBlock copy];
__weak VTVehicleServiceNetworkManager *weakSelf = self;
TaskBlock fetchOrdersListTaskBlock = ^{
NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:^{
HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
}];
[weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
};
[self fetchDataWithTaskBlock:fetchOrdersListTaskBlock
errorBlock:errorBlock];
}
Or better yet, re-design your class to work like this (I don't see a need for NSBlockOperation in your example):
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
completionBlock = [completionBlock copy];
errorBlock = [errorBlock copy];
TaskBlock fetchOrdersListTaskBlock = ^{
HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
};
[self fetchDataWithTaskBlock:fetchOrdersListTaskBlock
errorBlock:errorBlock];
}

How to load an Objective C Singleton using NSCoding?

I am writing a basic game, I am using a GameStateManager which is a singleton and handles all state management, such as saves, loads, delete methods.
I have a separate singelton which handles all the Game stuff. The game object is inside the game state manager so I can save it out using NSCoding and Archiving.
When I save the state there appears to be no issues and the Game object (singleton) is saved properly.
However, when I try to load the state (by relaunching the app), the game object is always null.
Strangley, if I remove the singleton properties and make it a standard class, this issue goes away and I can load the class and all its properties without any major issues.
In summary, I do this:
GameStateManager = Singleton, handles all game state management (load, save) and has a game object (game)
Game = Singleton which handles things within the game and has NSCoding protocol employed.
Saving the game state with the game object is fine, the object is clearly there.
Loading the game state seems to make the game object null. It should be there, but for some reason it never loads it.
If I remove all the properties that make the game class a singelton and make it a normal class, the issue seems to go away.
I think it has something to do with the fact that Game is never init'd, but this does not make sense because I can get Game to load when it has no singleton properties.
Code now follows.
// GameStateManager.m
-(void)saveGameState
{
CCLOG(#"METHOD: saveGameState()");
self.lastSaveDate = [NSDate date];
NSMutableData *data;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
NSKeyedArchiver *archiver;
BOOL result;
data = [NSMutableData data];
archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[archiver encodeObject:self.game forKey:#"game"];
[archiver finishEncoding];
result = [data writeToFile:archivePath atomically:YES];
[archiver release];
}
-(void)loadGameState
{
CCLOG(#"METHOD: loadGameState()");
NSData *data;
NSKeyedUnarchiver *unarchiver;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
data = [NSData dataWithContentsOfFile:archivePath];
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
// Customize unarchiver here
self.game = [unarchiver decodeObjectForKey:#"game"];
self.lastSaveDate = [unarchiver decodeObjectForKey:#"lastSaveDate"];
[unarchiver finishDecoding];
[unarchiver release];
CCLOG(#"Last Save Date = %#", self.lastSaveDate);
NSLog(#"game = %#", self.game);
}
// END OF GAMESTATEMANAGER
// -------------------------
// Game.h
#interface Game : NSObject
<NSCoding>
{
NSMutableArray *listOfCities;
NSMutableArray *listOfColors;
NSMutableArray *listOfPlayers;
}
#property (nonatomic, retain) NSMutableArray *listOfCities;
#property (nonatomic, retain) NSMutableArray *listOfColors;
#property (nonatomic, retain) NSMutableArray *listOfPlayers;
+(Game *) sharedGame;
//
// Game.m
// This is a cut-down version of the game object
// Game is a singelton
// The listOfCities, etc are arrays
//
#implementation Game
SYNTHESIZE_SINGLETON_FOR_CLASS(Game)
#synthesize listOfCities, listOfPlayers;
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
self.listOfCities = [aDecoder decodeObjectForKey:#"listOfCities"];
self.listOfPlayers = [aDecoder decodeObjectForKey:#"listOfPlayers"];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
// Archive objects
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
[aCoder encodeObject:self.listOfCities forKey:#"listOfCities"];
[aCoder encodeObject:self.listOfPlayers forKey:#"listOfPlayers"];
}
#end
My question is, how do I successfully save and load an Objective C singelton using NSCoding, and the Archiver?
Edit;
I have tried:
// In the GameStateManager
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.lastSaveDate = [[decoder decodeObjectForKey:#"lastSaveDate"] retain];
self.game = [[decoder decodeObjectForKey:#"game"] retain];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[encoder encodeObject:self.game forKey:#"game"];
}
and
// In Game.m
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
Since you're dealing with a singleton, you only ever want a single instance of the class to exist at any time. So you will want to archive and unarchive that single instance only.
Consider this code (assumes ARC is enabled):
#interface Singleton : NSObject <NSCoding>
+ (id)sharedInstance;
#property (strong, nonatomic) NSString *someString;
#end
#implementation Singleton
+ (id)sharedInstance {
static Singleton instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[Singleton alloc] init];
});
return instance;
}
#pragma mark - NSCoding implementation for singleton
- (id)initWithCoder:(NSCoder *)aDecoder {
// Unarchive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[instance setSomeString:[aDecoder decodeObjectForKey:#"someStringKey"]];
return instance;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
// Archive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[aCoder encodeObject:[instance someString] forKey:#"someStringKey"]];
}
#end
Using this template, you can archive/unarchive the singleton, like this:
// Archiving calls encodeWithCoder: on the singleton instance.
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:[Singleton sharedInstance]] forKey:#"key"];
// Unarchiving calls initWithCoder: on the singleton instance.
[[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"key"]];
try this in your initWithCoder method :
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
Easiest way:
1. Load singleton:
+ (AppState *)sharedInstance
{
static AppState *state = nil;
if ( !state )
{
// load NSData representation of your singleton here.
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"appStateData"];
if ( data )
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[AppState alloc] init];
}
}
return state;
}
2. Save singleton on a disk
- (BOOL)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
// now save NSData to disc or NSUserDefaults.
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"appStateData"];
}
3. Implement NSCoding methonds:
- (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;
Done!
I'm using a hybrid approach encompassing answers by Eric Baker and skywinder.
+ (GameStateManager *)sharedInstance {
static GameStateManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"key"];
if (data) {
instance = [NSKeyedUnarchiver unarchiveObjectWithData:data];
} else {
instance = [[GameStateManager alloc] init];
}
});
return instance;
}

Message wont connect to method

I'm kind of new to this, so it might be a beginners mistake. The problem is that when i send a message to a method it doesn't connect.
Here's the calling method:
-(BOOL) login:(LoginInfo *) info{
NSString *url = [NSString stringWithFormat:#"%#/%#?name=%#&password=%#", FAMORABLE_API_URL,FAMORABLE_API_ACTION_LOGIN, info.username, info.password];
NSDictionary* json = [self getJson:url];
NSString *token = [json objectForKey:#"Token"];
NSLog(#"results: %#", token);
LoginInfo *loginResult = [LoginInfo alloc];
loginResult.token = token;
//TODO
NSLog(#"test 1 %#", loginResult.token);
[clientService saveLoginInfo:loginResult];
return YES;
}
On the line above the last you can see I'm sending to saveLoginInfo in clientService which is declared in ClientService.h which is imported in this file.
-(void) saveLoginInfo:(LoginInfo *)info{
NSLog(#"test 2 %#", info);
NSLog(#"test 3%#", info.token);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:info.token forKey:KEY_TOKEN];
NSString *string = [defaults stringForKey:KEY_TOKEN];
NSLog(#"test 4%#", string);
// save it
[defaults synchronize];
if (!(info.token)){
self.currentUser = nil;
self.isLoggedOn=false;
}else{
self.currentUser = info;
self.isLoggedOn=true;
}
}
This is the method being called. I've put out a bunch of logs, mostly as a safe if i did one wrong, but none of them are being executed...
Do you have any ideas of what might be wrong?
Thanks in advance
Tom
Is clientService in the line [clientService saveLoginInfo:loginResult]; an instance variable in your class?
If so, make sure that you have instantiated the object somewhere before you call it. (Either in your class's init method, or possibly viewDidLoad if it's a UIViewController.)
E.g.
- (void)viewDidLoad {
[super viewDidLoad];
clientService = [[MyClientService alloc] init];
}
- (id)init {
self = [super init];
if (self) {
clientService = [[MyClientService alloc] init];
}
return self;
}
(Make sure you also release the instance in the dealloc method.

NSUserDefaults with Custom object that has an array of custom objects

I have an object that I convert into NSData using an NSKeyedArchiver and then store it into NSUserDefaults. Everything gets saved correctly except for the elements of an array that the object has. All the objects in the array have conform to the NSCoder protocols (or whatever theyre called- ex. self.property = [decoder decodeObjectForKey:#"key"] and [encoder encodeObjectForKey:#"key"])
When I save the object, the elements of the array remain in the array, but their properties themselves do not get saved. i DO call the sycnrhonize method, so that is not the issue.
NOTE that all other times i save & load it is correct, it just does not save the elements of an array that belongs to an object. Do i have to save that separately?
The "current status" NSNumber is not being saved. Objective and target are being saved
import "Level.h"
#implementation Level
#synthesize objective = _objective;
#synthesize isComplete = _isComplete;
#synthesize goldReward = _goldReward;
#synthesize xpReward = _xpReward;
#synthesize missionID = _missionID;
#synthesize currentStatus = _currentStatus;
#synthesize targetName = _targetName;
#synthesize owner = _owner;
-(void)dealloc{
[super dealloc];
}
-(id)initWithMissionID:(int)number{
if (self = [super init]) {
self.currentStatus = 0;
self.isComplete = NO;
self.missionID = [NSNumber numberWithInt:number];
[self setUpMisson];
}
return self;
}
-(void)setUpMisson{
if ([self.missionID intValue] == 0) {
self.xpReward = [NSNumber numberWithInt:100];
self.goldReward = [NSNumber numberWithInt:100];
self.objective = [NSNumber numberWithInt:3];
self.targetName = #"Swordsman";
CCLOG(#"Gotta kill some swordsmen!");
}
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.objective forKey:#"objective"];
[encoder encodeObject:self.isComplete forKey:#"isComplete"];
[encoder encodeObject:self.goldReward forKey:#"goldReward"];
[encoder encodeObject:self.xpReward forKey:#"xpReward"];
[encoder encodeObject:self.missionID forKey:#"missionID"];
[encoder encodeObject:self.currentStatus forKey:#"currentStatus"];
[encoder encodeObject:self.targetName forKey:#"targetName"];
[encoder encodeObject:self.owner forKey:#"owner"];
CCLOG(#"SAVING LEVEL");
}
-(id)initWithCoder:(NSCoder *)decoder{
if (self = [super init]) {
self.objective = [[decoder decodeObjectForKey:#"objective"]retain];
self.isComplete = [[decoder decodeObjectForKey:#"isComplete"]retain];
self.goldReward = [[decoder decodeObjectForKey:#"goldReward"]retain];
self.xpReward = [[decoder decodeObjectForKey:#"xpReward"]retain];
self.missionID = [[decoder decodeObjectForKey:#"missionID"]retain];
self.targetName = [[decoder decodeObjectForKey:#"targetName"]retain];
self.owner = [[decoder decodeObjectForKey:#"owner"]retain];
CCLOG(#"LOADING LEVEL");
}
return self;
}
-(void)updateStatusForKill:(AI *)killedTarget{
CCLOG(#"WE KILLED: %# and OUR GOAL IS: %#",killedTarget.name,self.targetName);
if ([killedTarget.name isEqualToString:self.targetName]) {
[self setCurrentStatus:[NSNumber numberWithInt:[self.currentStatus intValue]+1]];
CCLOG(#"Current Status: %i Objective: %i", [self.currentStatus intValue],[self.objective intValue]);
if ([self.currentStatus intValue] == [self.objective intValue]) {
[self completeMission];
}
}
}
-(void)completeMission{
[self.owner setCoins:[NSNumber numberWithInt:[[self.owner coins]intValue] + [self.goldReward intValue]]];
[self.owner setXp:[NSNumber numberWithInt:[[self.owner xp]intValue] + [self.xpReward intValue]]];
CCLOG(#"complete");
[[self.owner missionList]removeObject:self];
}
#end
EDIT: The "owner" refers back to the object being saved. I think this is where the problem is, so I'm removing that and testing again.
EDIT: and that did nothing!
What you describe should "just work." In the encodeWithCoder method of your custom object, you would just encode the array object, and that should cause the array and it's contents to be encoded.
However, if any of the objects in the array do not support NSCoding, that will fail. My guess is that something in your array (or it's children or grandchildren) does not support NSCoding.
I ran into this problem when trying to save an array of Accounts that contain property values and another custom object. I couldn't save my data with your proposed solution because I was arbitrarily adding accounts to an array, and it wouldn't make sense to come up with unique identifiers for dynamically added accounts. I ended up nesting the NSCoding protocol:
In my AccountManager class:
- (void) saveAllAccounts {
[self deleteAllAccounts]; //Just removes whatever was previously stored there
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:accountArray] forKey:saveAccountsArrayKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
In my Account class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:token forKey:#"token"];
[aCoder encodeObject:specialID forKey:#"special ID"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:deviceArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"device array"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
username = [[aDecoder decodeObjectForKey:#"username"] retain];
token = [[aDecoder decodeObjectForKey:#"token"] retain];
ecoID = [[aDecoder decodeObjectForKey:#"eco ID"] retain];
NSData *deviceArrayData = [[NSUserDefaults standardUserDefaults] objectForKey:#"device array"];
if (deviceArrayData != nil) {
NSArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData: deviceArrayData];
if (savedArray != nil)
deviceArray = [[NSMutableArray alloc] initWithArray:savedArray];
}
}
return self;
}
In my AccountDevice class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:pairingPassword forKey:#"pairing password"];
[aCoder encodeObject:devicePin forKey:#"device pin"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
password = [[aDecoder decodeObjectForKey:#"password"] retain];
pin = [[aDecoder decodeObjectForKey:#"pin"] retain];
}
return self;
}
It might be a little buggy because I haven't finished testing it, but my preliminary tests were successful, and I think the concept is there.
It looks like you're using -encodeObject:forKey: and -decodeObjectForKey: even on values that aren't objects. For example, in your -initWithMissionID: you've got:
self.isComplete = NO;
which makes me think that complete is a BOOL property, but your -encodeObject:forKey: says:
[encoder encodeObject:self.isComplete forKey:#"isComplete"];
I think you probably want to call -encodeBool:forKey: instead, like this:
[encoder encodeBool:self.isComplete forKey:#"isComplete"];
On the other hand, I'd really expect some sort of warning if the problem were that simple. Do you get any warnings? It's harder to infer the types of your other properties, but look at each of your properties for the same kind of problem.
found a workaround using a Unique-Id system for each mission and saving the progress for each mission separately into NSUserDefaults which are just then loaded again. Not ideal, but it works. Thanks for everyone's help!

Singleton object memory issue in objective c

I've been stuck on this one for a while and any advice would be greatly appreciated. I've created a singleton object called SharedUser that represents the user who is currently using the app. This SharedUser object has two NSStrings (for username and userId) and an NSArray (for the company departments to which the user belongs). The user is initialized upon login with a method called initWithUsername. Inside this method the username is used to parse the user and get the user's unique id number and the the user's departments.
My problem is as follows: when I get the user through the sharedUser method at a later point in the app, the userId string is empty and causes an exc_bad_access error when I try to use it. I know that the userId string is being initialized though because I have observed this happening in the debugger. However, strangely enough, the username string still exists for the same object that is lacking the userId string. I'm very confused as to how the memory behind userId could be released while username would still hang around.
My SharedUser class looks like this:
#implementation SharedUser
#synthesize userId;
#synthesize username;
#synthesize departmentIds;
static SharedUser *sharedUser = nil;
+(SharedUser *)sharedUser {
#synchronized(self) {
if (!sharedUser) {
sharedUser=[[self alloc] init];
}
}
return sharedUser;
}
+ (id)allocWithZone:(NSZone *)zone {
#synchronized(self) {
if (sharedUser == nil) {
sharedUser = [super allocWithZone:zone];
return sharedUser;
}
}
return nil;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return NSUIntegerMax;
}
- (oneway void)release {
// never release
}
- (id)autorelease {
//do nothing
}
- (id) init {
#synchronized(self) {
[super init];
username = #"";
userId = #"";
departmentIds = nil;
return self;
}
}
- (void) initWithUserName:(NSString *) loginName {
username = loginName;
//create a request and a connection
NSURL *url = [NSURL URLWithString:#"http://blah.com/url/for/the/json/"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *responseString = [request responseString];
NSArray *listOfPeopleDictionaries = [responseString JSONValue];
for (NSDictionary *personDictionary in listOfPeopleDictionaries) {
if ([loginName isEqualToString:[personDictionary objectForKey:#"username"]]) {
// Set the user id
userId = [personDictionary objectForKey:#"id"];
// Set the user's department
NSArray *departmentsArray = [personDictionary objectForKey:#"organizations"];
for (NSDictionary *department in departmentsArray) {
[departmentIds addObject:[department objectForKey:#"id"]];
}
}
}
}
else {
//give error
}
}
- (void)dealloc {
// Should never be called
[username release];
[userId release];
[departmentIds release];
[super dealloc];
}
The following code would cause an exc_bad_access error on the third line.
SharedUser *user = [SharedUser sharedUser];
NSLog(#"%#", user.username);
NSLog(#"%#", user.userId);
That said, my question is where is the memory behind the userId string being released and what do I do to fix it? I'm brand new to stack overflow so please be gentle.
userId is not being retained by your singleton. The value is owned by personDictionary, which is owned by listOfPeopleDictionaries, which is an autoreleased return value from [responseString JSONValue]. When the autorelease pool is purged the whole chain is released including userId.
The solution is to retain userId, either by doing
userId = [[personDictionary objectForKey:#"id"] retain];
or
self.userId = [personDictionary objectForKey:#"id"]
username has the same issue, but probably isn't crashing as you're holding on to it in the object which is calling initWithUserName:.
I would probably go with another solution: "normal" class, that offers one shared instance, while it doesn't bother about all the memory (non-)management
And I want to propose the new grand central dispatch approach
#implementation User
#synthesize userId;
#synthesize username;
#synthesize departmentIds;
+ (User *)sharedUser {
static User *_sharedUser = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedUser = [[self alloc] init…];
//what ever is needed to get a proper shared user
});
return _sharedUser;
}
#end
More is not needed, if you are ok with a less strict version of a singleton.
This is also much easier to handle in Unit-testing.
User *sharedUser = [User sharedUser];
to fix your code, please try
username = [loginName copy]; //or retain, if you use retain in ur property
instead of
username = loginName;